tir-framework 2.5.0__tar.gz → 2.7.0__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 (40) hide show
  1. {tir_framework-2.5.0/tir_framework.egg-info → tir_framework-2.7.0}/PKG-INFO +2 -2
  2. {tir_framework-2.5.0 → tir_framework-2.7.0}/README.md +1 -1
  3. tir_framework-2.7.0/tests/test_log.py +123 -0
  4. tir_framework-2.7.0/tests/test_utils.py +238 -0
  5. tir_framework-2.7.0/tests/test_webapp_internal_clicktree.py +431 -0
  6. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/main.py +176 -17
  7. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/base.py +88 -14
  8. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/config.py +4 -1
  9. tir_framework-2.7.0/tir/technologies/core/events.py +23 -0
  10. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/language.py +78 -6
  11. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/log.py +76 -4
  12. tir_framework-2.7.0/tir/technologies/core/router.py +157 -0
  13. tir_framework-2.7.0/tir/technologies/core/utils.py +74 -0
  14. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/poui_internal.py +1573 -256
  15. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/webapp_internal.py +1949 -1234
  16. tir_framework-2.7.0/tir/version.py +1 -0
  17. {tir_framework-2.5.0 → tir_framework-2.7.0/tir_framework.egg-info}/PKG-INFO +2 -2
  18. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir_framework.egg-info/SOURCES.txt +6 -0
  19. tir_framework-2.5.0/tir/version.py +0 -1
  20. {tir_framework-2.5.0 → tir_framework-2.7.0}/LICENSE +0 -0
  21. {tir_framework-2.5.0 → tir_framework-2.7.0}/MANIFEST.in +0 -0
  22. {tir_framework-2.5.0 → tir_framework-2.7.0}/setup.cfg +0 -0
  23. {tir_framework-2.5.0 → tir_framework-2.7.0}/setup.py +0 -0
  24. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/__init__.py +0 -0
  25. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/apw_internal.py +0 -0
  26. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/base_database.py +0 -0
  27. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/data/sx3.csv +0 -0
  28. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/drivers/linux64/geckodriver +0 -0
  29. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/drivers/windows/chromedriver.exe +0 -0
  30. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/drivers/windows/electron/chromedriver.exe +0 -0
  31. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/drivers/windows/geckodriver.exe +0 -0
  32. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/enumerations.py +0 -0
  33. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/logging_config.py +0 -0
  34. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/numexec.py +0 -0
  35. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/psutil_info.py +0 -0
  36. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/third_party/screen_size.py +0 -0
  37. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir/technologies/core/third_party/xpath_soup.py +0 -0
  38. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir_framework.egg-info/dependency_links.txt +0 -0
  39. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir_framework.egg-info/requires.txt +0 -0
  40. {tir_framework-2.5.0 → tir_framework-2.7.0}/tir_framework.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tir_framework
3
- Version: 2.5.0
3
+ Version: 2.7.0
4
4
  Summary: Test Interface Robot
5
5
  Home-page: https://github.com/totvs/tir
6
6
  Download-URL: https://github.com/totvs/tir
@@ -192,7 +192,7 @@ Após a configuração do ambiente virtual iremos definir o interpretador do VSc
192
192
 
193
193
  ## Contact
194
194
 
195
- [Gitter](https://gitter.im/totvs-tir/General)
195
+ [Gitter](https://app.element.io/#/room/#totvs-tir_General:gitter.im)
196
196
 
197
197
  ## Contributing
198
198
 
@@ -140,7 +140,7 @@ Após a configuração do ambiente virtual iremos definir o interpretador do VSc
140
140
 
141
141
  ## Contact
142
142
 
143
- [Gitter](https://gitter.im/totvs-tir/General)
143
+ [Gitter](https://app.element.io/#/room/#totvs-tir_General:gitter.im)
144
144
 
145
145
  ## Contributing
146
146
 
@@ -0,0 +1,123 @@
1
+ import tempfile
2
+ from pathlib import Path
3
+ import types
4
+ import uuid
5
+
6
+ import pytest
7
+
8
+ import importlib.util
9
+
10
+ # Load `Log` directly from file to avoid importing package-level dependencies
11
+ repo_root = Path(__file__).resolve().parents[1]
12
+ log_path = repo_root / 'tir' / 'technologies' / 'core' / 'log.py'
13
+ spec = importlib.util.spec_from_file_location('tir_log', str(log_path))
14
+ mod = importlib.util.module_from_spec(spec)
15
+
16
+ # Inject minimal tir package stubs to avoid importing heavy dependencies during tests
17
+ import sys
18
+ pkg = types.ModuleType('tir')
19
+ sys.modules['tir'] = pkg
20
+ sys.modules['tir.technologies'] = types.ModuleType('tir.technologies')
21
+ sys.modules['tir.technologies.core'] = types.ModuleType('tir.technologies.core')
22
+
23
+ # Minimal ConfigLoader stub
24
+ cfg_mod = types.ModuleType('tir.technologies.core.config')
25
+ class DummyConfig:
26
+ def __init__(self, path=None):
27
+ self.issue = ''
28
+ self.execution_id = ''
29
+ self.country = 'BRA'
30
+ self.release = ''
31
+ self.logurl1 = ''
32
+ self.logurl2 = ''
33
+ self.smart_test = False
34
+ self.log_http = ''
35
+ self.num_exec = None
36
+ self.api_url = ''
37
+ self.api_url_ip = ''
38
+ self.api_json_path = str(repo_root / 'Log')
39
+ self.debug_log = False
40
+ cfg_mod.ConfigLoader = lambda path=None: DummyConfig(path)
41
+ sys.modules['tir.technologies.core.config'] = cfg_mod
42
+
43
+ # Minimal logging_config stub
44
+ logcfg_mod = types.ModuleType('tir.technologies.core.logging_config')
45
+ import logging
46
+ def logger():
47
+ return logging.getLogger('test_logger')
48
+ logcfg_mod.logger = logger
49
+ sys.modules['tir.technologies.core.logging_config'] = logcfg_mod
50
+
51
+ spec.loader.exec_module(mod)
52
+ Log = mod.Log
53
+
54
+
55
+ def make_log_with_cfg(tmp_dir: Path):
56
+ l = Log(user='tester', station='station')
57
+ class Cfg:
58
+ pass
59
+ cfg = Cfg()
60
+ cfg.log_http = str(tmp_dir)
61
+ cfg.country = 'BRA'
62
+ cfg.release = '1.0'
63
+ cfg.issue = 'ISS'
64
+ cfg.execution_id = 'EID'
65
+ cfg.debug_log = False
66
+ l.config = cfg
67
+ return l
68
+
69
+
70
+ def test_diagnostic_io_reports(tmp_path):
71
+ l = make_log_with_cfg(tmp_path)
72
+ res = l.diagnostic_io(tmp_path)
73
+ assert 'user' in res
74
+ assert 'target' in res
75
+ assert res['exists'] is True
76
+ assert isinstance(res['readable'], bool)
77
+ assert isinstance(res['writable'], bool)
78
+
79
+
80
+ def test_take_screenshot_writes_file(tmp_path):
81
+ l = make_log_with_cfg(tmp_path)
82
+
83
+ # Prepare a fake driver that actually writes the file so we can assert existence
84
+ captured = []
85
+ def save_screenshot(path):
86
+ captured.append(path)
87
+ Path(path).write_text('screenshot')
88
+ return True
89
+
90
+ driver = types.SimpleNamespace()
91
+ driver.save_screenshot = save_screenshot
92
+
93
+ screenshot_name = 'my_test'
94
+ stack_item = f"testcase_{uuid.uuid4().hex[:6]}"
95
+
96
+ l.take_screenshot_log(driver, description=screenshot_name, stack_item=stack_item)
97
+
98
+ testsuite = l.get_file_name("testsuite")
99
+ folder_path = Path(l.config.log_http, l.config.country, l.release, l.config.issue, l.config.execution_id, testsuite)
100
+ # Assert that driver was called and file was created at that path
101
+ assert captured, "Driver.save_screenshot was not called"
102
+ written_path = Path(captured[0])
103
+ assert written_path.exists()
104
+
105
+
106
+ def test_take_screenshot_fallback_on_write_fail(tmp_path, monkeypatch):
107
+ l = make_log_with_cfg(tmp_path)
108
+
109
+ # Simulate write failure (e.g., directory read-only)
110
+ def failing_save_screenshot(path):
111
+ raise PermissionError("Simulated write fail")
112
+
113
+ driver = types.SimpleNamespace()
114
+ driver.save_screenshot = failing_save_screenshot
115
+
116
+ # Monkeypatch tempfile.gettempdir to a controlled dir
117
+ temp_dir = tmp_path / 'fallback'
118
+ temp_dir.mkdir()
119
+ monkeypatch.setattr('tempfile.gettempdir', lambda: str(temp_dir))
120
+
121
+ # Should not crash; should log and continue
122
+ l.take_screenshot_log(driver, description='fail_test', stack_item='testcase_123')
123
+ # Verify if file was created in fallback (if applicable)
@@ -0,0 +1,238 @@
1
+ """Unit tests for Utils class and its methods."""
2
+
3
+ import inspect
4
+ import os
5
+ import sys
6
+ import unittest
7
+ from unittest.mock import MagicMock, patch
8
+ from pathlib import Path
9
+
10
+ # Add the parent directory to the path so we can import tir module
11
+ repo_root = Path(__file__).resolve().parents[1]
12
+ sys.path.insert(0, str(repo_root))
13
+
14
+ from tir.technologies.core.utils import Utils
15
+
16
+
17
+ class TestUtilsGetMainEntrypointFromStack(unittest.TestCase):
18
+ """Test cases for Utils.get_main_entrypoint_from_stack method."""
19
+
20
+ def setUp(self):
21
+ """Initialize test fixtures."""
22
+ self.utils = Utils()
23
+
24
+ def test_get_main_entrypoint_with_default_parameters(self):
25
+ """Test method returns a string value with default parameters."""
26
+ result = self.utils.get_main_entrypoint_from_stack()
27
+ self.assertIsInstance(result, str)
28
+
29
+ def test_get_main_entrypoint_returns_fallback_when_no_match(self):
30
+ """Test method returns fallback value when no matching frame is found."""
31
+ result = self.utils.get_main_entrypoint_from_stack(
32
+ target_modules=["nonexistent/module.py"],
33
+ fallback="custom_fallback"
34
+ )
35
+ self.assertEqual(result, "custom_fallback")
36
+
37
+ def test_get_main_entrypoint_returns_default_fallback(self):
38
+ """Test method returns default fallback when no matching frame is found."""
39
+ result = self.utils.get_main_entrypoint_from_stack(
40
+ target_modules=["nonexistent/module.py"]
41
+ )
42
+ self.assertEqual(result, "function_name")
43
+
44
+ def test_get_main_entrypoint_ignores_specified_functions(self):
45
+ """Test that specified functions in ignored_functions are skipped."""
46
+ # Create a set of functions to ignore
47
+ ignored_set = {"__getattribute__", "_subscribe_routes", "__init__", "<lambda>"}
48
+
49
+ result = self.utils.get_main_entrypoint_from_stack(
50
+ target_modules=["test_utils.py"],
51
+ ignored_functions=ignored_set,
52
+ fallback="test_fallback"
53
+ )
54
+
55
+ # The result should not be in the ignored set or should be fallback
56
+ if result != "test_fallback":
57
+ self.assertNotIn(result, ignored_set)
58
+
59
+ def test_get_main_entrypoint_with_custom_ignored_functions(self):
60
+ """Test method with custom ignored functions set."""
61
+ custom_ignored = {"test_method", "setup"}
62
+ result = self.utils.get_main_entrypoint_from_stack(
63
+ target_modules=["test_utils.py"],
64
+ ignored_functions=custom_ignored,
65
+ fallback="not_found"
66
+ )
67
+
68
+ self.assertIsInstance(result, str)
69
+
70
+ def test_get_main_entrypoint_case_insensitive_matching(self):
71
+ """Test that module matching is case-insensitive."""
72
+ # Test with uppercase module name
73
+ result1 = self.utils.get_main_entrypoint_from_stack(
74
+ target_modules=["TEST_UTILS.PY"],
75
+ fallback="not_found"
76
+ )
77
+
78
+ # Test with lowercase module name
79
+ result2 = self.utils.get_main_entrypoint_from_stack(
80
+ target_modules=["test_utils.py"],
81
+ fallback="not_found"
82
+ )
83
+
84
+ # Both should return the same result
85
+ self.assertEqual(result1, result2)
86
+
87
+ def test_get_main_entrypoint_with_path_separators(self):
88
+ """Test that method handles both forward and backward slashes."""
89
+ # Test with forward slashes
90
+ result1 = self.utils.get_main_entrypoint_from_stack(
91
+ target_modules=["tests/test_utils.py"],
92
+ fallback="not_found"
93
+ )
94
+
95
+ # Test with backward slashes (Windows style)
96
+ result2 = self.utils.get_main_entrypoint_from_stack(
97
+ target_modules=["tests\\test_utils.py"],
98
+ fallback="not_found"
99
+ )
100
+
101
+ # Both should return the same result
102
+ self.assertEqual(result1, result2)
103
+
104
+ def test_get_main_entrypoint_multiple_target_modules(self):
105
+ """Test method with multiple target modules."""
106
+ result = self.utils.get_main_entrypoint_from_stack(
107
+ target_modules=["nonexistent.py", "test_utils.py", "another.py"],
108
+ fallback="not_found"
109
+ )
110
+
111
+ self.assertIsInstance(result, str)
112
+
113
+ @patch('inspect.stack')
114
+ def test_get_main_entrypoint_with_mocked_stack(self, mock_stack):
115
+ """Test method behavior with mocked call stack."""
116
+ # Create mock stack frames
117
+ mock_frame1 = MagicMock()
118
+ mock_frame1.filename = "c:\\TOTVS\\repos\\tir\\tir\\main.py"
119
+ mock_frame1.function = "test_function"
120
+
121
+ mock_frame2 = MagicMock()
122
+ mock_frame2.filename = "c:\\other\\path\\module.py"
123
+ mock_frame2.function = "other_function"
124
+
125
+ mock_stack.return_value = [mock_frame1, mock_frame2]
126
+
127
+ result = self.utils.get_main_entrypoint_from_stack(
128
+ target_modules=["tir/main.py"]
129
+ )
130
+
131
+ self.assertEqual(result, "test_function")
132
+
133
+ @patch('inspect.stack')
134
+ def test_get_main_entrypoint_skips_ignored_functions(self, mock_stack):
135
+ """Test that method skips frames with ignored functions."""
136
+ # Create mock stack frames
137
+ mock_frame1 = MagicMock()
138
+ mock_frame1.filename = "c:\\TOTVS\\repos\\tir\\tir\\main.py"
139
+ mock_frame1.function = "__init__" # This should be ignored
140
+
141
+ mock_frame2 = MagicMock()
142
+ mock_frame2.filename = "c:\\TOTVS\\repos\\tir\\tir\\main.py"
143
+ mock_frame2.function = "user_function" # This should be returned
144
+
145
+ mock_stack.return_value = [mock_frame1, mock_frame2]
146
+
147
+ result = self.utils.get_main_entrypoint_from_stack(
148
+ target_modules=["tir/main.py"]
149
+ )
150
+
151
+ self.assertEqual(result, "user_function")
152
+
153
+ @patch('inspect.stack')
154
+ def test_get_main_entrypoint_returns_first_match(self, mock_stack):
155
+ """Test that method returns the first matching function."""
156
+ # Create mock stack frames
157
+ mock_frame1 = MagicMock()
158
+ mock_frame1.filename = "c:\\TOTVS\\repos\\tir\\tir\\main.py"
159
+ mock_frame1.function = "first_function"
160
+
161
+ mock_frame2 = MagicMock()
162
+ mock_frame2.filename = "c:\\TOTVS\\repos\\tir\\tir\\main.py"
163
+ mock_frame2.function = "second_function"
164
+
165
+ mock_stack.return_value = [mock_frame1, mock_frame2]
166
+
167
+ result = self.utils.get_main_entrypoint_from_stack(
168
+ target_modules=["tir/main.py"]
169
+ )
170
+
171
+ # Should return the first matching function
172
+ self.assertEqual(result, "first_function")
173
+
174
+ def test_get_main_entrypoint_empty_ignored_functions(self):
175
+ """Test method with empty ignored functions set."""
176
+ result = self.utils.get_main_entrypoint_from_stack(
177
+ target_modules=["test_utils.py"],
178
+ ignored_functions=set(),
179
+ fallback="fallback"
180
+ )
181
+
182
+ self.assertIsInstance(result, str)
183
+
184
+ def test_get_main_entrypoint_returns_string_type(self):
185
+ """Test that method always returns a string."""
186
+ test_cases = [
187
+ {"target_modules": None},
188
+ {"target_modules": ["nonexistent.py"]},
189
+ {"fallback": "custom"},
190
+ {"ignored_functions": set()},
191
+ ]
192
+
193
+ for kwargs in test_cases:
194
+ result = self.utils.get_main_entrypoint_from_stack(**kwargs)
195
+ self.assertIsInstance(result, str,
196
+ f"Expected str, got {type(result)} for kwargs: {kwargs}")
197
+
198
+ def test_utils_instantiation(self):
199
+ """Test that Utils class can be instantiated."""
200
+ utils_instance = Utils()
201
+ self.assertIsNotNone(utils_instance)
202
+ self.assertTrue(hasattr(utils_instance, 'get_main_entrypoint_from_stack'))
203
+
204
+ def test_get_main_entrypoint_with_absolute_path_module(self):
205
+ """Test method with absolute path in target_modules."""
206
+ abs_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "test_utils.py"))
207
+
208
+ result = self.utils.get_main_entrypoint_from_stack(
209
+ target_modules=[abs_path],
210
+ fallback="not_found"
211
+ )
212
+
213
+ self.assertIsInstance(result, str)
214
+
215
+
216
+ class TestUtilsClass(unittest.TestCase):
217
+ """Test cases for Utils class itself."""
218
+
219
+ def test_utils_has_get_main_entrypoint_method(self):
220
+ """Test that Utils class has the get_main_entrypoint_from_stack method."""
221
+ utils = Utils()
222
+ self.assertTrue(hasattr(utils, 'get_main_entrypoint_from_stack'))
223
+ self.assertTrue(callable(getattr(utils, 'get_main_entrypoint_from_stack')))
224
+
225
+ def test_utils_docstring_exists(self):
226
+ """Test that Utils class has proper documentation."""
227
+ self.assertIsNotNone(Utils.__doc__)
228
+ self.assertTrue(len(Utils.__doc__) > 0)
229
+
230
+ def test_get_main_entrypoint_docstring_exists(self):
231
+ """Test that method has proper documentation."""
232
+ method = Utils.get_main_entrypoint_from_stack
233
+ self.assertIsNotNone(method.__doc__)
234
+ self.assertTrue(len(method.__doc__) > 0)
235
+
236
+
237
+ if __name__ == '__main__':
238
+ unittest.main()