revitdevtool-pytest 0.0.2__tar.gz → 0.0.3__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.
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/CHANGELOG.md +12 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/PKG-INFO +1 -1
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/pyproject.toml +1 -1
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/dialog_resolver.py +37 -10
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/discovery.py +13 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/plugin.py +11 -8
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/.github/workflows/publish.yml +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/.gitignore +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/.vscode/settings.json +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/LICENSE +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/README.md +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/__init__.py +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/bridge.py +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/constants.py +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/models.py +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/serializer.py +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/tests/conftest.py +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/tests/test_revit_basic.py +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/tests/test_revit_transactions.py +0 -0
- {revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/tests/test_smoke.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.0.3] - 2026-04-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed Revit auto-launch dialog handling by starting the startup dialog resolver immediately after spawning Revit, before waiting for the named pipe.
|
|
10
|
+
- Fixed startup dialog button selection to prefer safe allow-list actions such as `Always Load` and `Load Once`.
|
|
11
|
+
- Prevented accidental clicks on destructive dialog actions such as `Do Not Load`, `Cancel`, and `No`.
|
|
12
|
+
|
|
13
|
+
### Notes
|
|
14
|
+
|
|
15
|
+
- This release focuses on reliable auto-launch behavior when Revit shows unsigned add-in security dialogs during startup.
|
|
16
|
+
|
|
5
17
|
## [0.0.2] - 2026-04-10
|
|
6
18
|
|
|
7
19
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: revitdevtool_pytest
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: pytest plugin for testing Revit API code via RevitDevTool Named Pipe bridge
|
|
5
5
|
Project-URL: Homepage, https://github.com/trgiangv/RevitDevTool.PyTest
|
|
6
6
|
Project-URL: Repository, https://github.com/trgiangv/RevitDevTool.PyTest
|
{revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/dialog_resolver.py
RENAMED
|
@@ -59,7 +59,10 @@ class DialogResolverOptions:
|
|
|
59
59
|
"Autodesk", "Revit", "Load", "Security", "Warning", "Add-in", "Addin",
|
|
60
60
|
])
|
|
61
61
|
preferred_button_keywords: list[str] = field(default_factory=lambda: [
|
|
62
|
-
"Always Load", "Load", "OK", "Yes", "Accept", "Continue", "Close",
|
|
62
|
+
"Always Load", "Load Once", "Load", "OK", "Yes", "Accept", "Continue", "Close",
|
|
63
|
+
])
|
|
64
|
+
blocked_button_keywords: list[str] = field(default_factory=lambda: [
|
|
65
|
+
"Do Not Load", "Cancel", "No",
|
|
63
66
|
])
|
|
64
67
|
|
|
65
68
|
|
|
@@ -106,17 +109,25 @@ class StartupDialogResolver:
|
|
|
106
109
|
return any(kw.lower() in lower for kw in self._opts.dialog_title_keywords)
|
|
107
110
|
|
|
108
111
|
def _find_button(self, parent: int) -> int | None:
|
|
109
|
-
result: list[int] = []
|
|
112
|
+
result: list[tuple[int, int]] = []
|
|
110
113
|
|
|
111
114
|
@_EnumWindowsProc
|
|
112
115
|
def _cb(child: int, _: int) -> bool:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
score = _get_button_score(
|
|
117
|
+
child,
|
|
118
|
+
self._opts.preferred_button_keywords,
|
|
119
|
+
self._opts.blocked_button_keywords,
|
|
120
|
+
)
|
|
121
|
+
if score is not None:
|
|
122
|
+
result.append((score, child))
|
|
116
123
|
return True
|
|
117
124
|
|
|
118
125
|
_user32.EnumChildWindows(parent, _cb, 0)
|
|
119
|
-
|
|
126
|
+
if not result:
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
result.sort(key=lambda item: item[0])
|
|
130
|
+
return result[0][1]
|
|
120
131
|
|
|
121
132
|
def _enum_dialog_windows(self) -> list[int]:
|
|
122
133
|
windows: list[int] = []
|
|
@@ -131,14 +142,30 @@ class StartupDialogResolver:
|
|
|
131
142
|
return windows
|
|
132
143
|
|
|
133
144
|
|
|
134
|
-
def
|
|
145
|
+
def _get_button_score(hwnd: int, keywords: list[str], blocked_keywords: list[str]) -> int | None:
|
|
135
146
|
if _get_class_name(hwnd).lower() != _BUTTON_CLASS:
|
|
136
|
-
return
|
|
147
|
+
return None
|
|
148
|
+
|
|
137
149
|
text = _get_window_text(hwnd)
|
|
138
150
|
if not text:
|
|
139
|
-
return
|
|
151
|
+
return None
|
|
152
|
+
|
|
140
153
|
text_lower = text.lower()
|
|
141
|
-
|
|
154
|
+
|
|
155
|
+
if any(kw.lower() in text_lower for kw in blocked_keywords):
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
for index, keyword in enumerate(keywords):
|
|
159
|
+
keyword_lower = keyword.lower()
|
|
160
|
+
if text_lower == keyword_lower:
|
|
161
|
+
return index
|
|
162
|
+
|
|
163
|
+
for index, keyword in enumerate(keywords):
|
|
164
|
+
keyword_lower = keyword.lower()
|
|
165
|
+
if keyword_lower in text_lower:
|
|
166
|
+
return index + len(keywords)
|
|
167
|
+
|
|
168
|
+
return None
|
|
142
169
|
|
|
143
170
|
|
|
144
171
|
def _is_target_dialog(hwnd: int, target_pid: int) -> bool:
|
{revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/discovery.py
RENAMED
|
@@ -87,6 +87,19 @@ def launch_revit(
|
|
|
87
87
|
return wait_for_revit_pipe(version, timeout_s=wait_timeout_s)
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
def start_revit(version: int) -> int:
|
|
91
|
+
"""Start Revit with ``/nosplash`` and return the spawned process id."""
|
|
92
|
+
exe_path = find_revit_path(version)
|
|
93
|
+
if exe_path is None:
|
|
94
|
+
raise FileNotFoundError(f"Revit {version} installation not found.")
|
|
95
|
+
|
|
96
|
+
process = subprocess.Popen( # noqa: S603
|
|
97
|
+
[exe_path, REVIT_NOSPLASH],
|
|
98
|
+
creationflags=subprocess.DETACHED_PROCESS,
|
|
99
|
+
)
|
|
100
|
+
return int(process.pid)
|
|
101
|
+
|
|
102
|
+
|
|
90
103
|
def wait_for_revit_pipe(
|
|
91
104
|
version: int | None = None,
|
|
92
105
|
timeout_s: float = DEFAULT_LAUNCH_TIMEOUT_S,
|
|
@@ -22,7 +22,7 @@ from .constants import (
|
|
|
22
22
|
OPT_VERSION,
|
|
23
23
|
PLUGIN_NAME,
|
|
24
24
|
)
|
|
25
|
-
from .discovery import find_revit_path, find_revit_pipes,
|
|
25
|
+
from .discovery import find_revit_path, find_revit_pipes, select_instance, start_revit, wait_for_revit_pipe
|
|
26
26
|
from .models import TestOutcome
|
|
27
27
|
from .serializer import serialize_test
|
|
28
28
|
|
|
@@ -152,22 +152,25 @@ def _auto_launch(version: int, config: pytest.Config) -> RevitInstance:
|
|
|
152
152
|
)
|
|
153
153
|
|
|
154
154
|
print(f"{PLUGIN_NAME}: Launching Revit {version}...")
|
|
155
|
-
instance = launch_revit(version, wait_timeout_s=timeout)
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
pytest.exit(
|
|
159
|
-
f"{PLUGIN_NAME}: Revit {version} launched but Named Pipe did not appear within {timeout}s.",
|
|
160
|
-
returncode=EXIT_CODE_CONFIG_ERROR,
|
|
161
|
-
)
|
|
156
|
+
process_id = start_revit(version)
|
|
162
157
|
|
|
163
158
|
try:
|
|
164
159
|
from .dialog_resolver import StartupDialogResolver as _Resolver
|
|
165
160
|
|
|
166
|
-
_dialog_resolver = _Resolver(
|
|
161
|
+
_dialog_resolver = _Resolver(process_id)
|
|
167
162
|
_dialog_resolver.start()
|
|
168
163
|
except ImportError:
|
|
169
164
|
pass
|
|
170
165
|
|
|
166
|
+
instance = wait_for_revit_pipe(version, timeout_s=timeout)
|
|
167
|
+
|
|
168
|
+
if instance is None:
|
|
169
|
+
pytest.exit(
|
|
170
|
+
f"{PLUGIN_NAME}: Revit {version} launched but Named Pipe did not appear within {timeout}s.",
|
|
171
|
+
returncode=EXIT_CODE_CONFIG_ERROR,
|
|
172
|
+
)
|
|
173
|
+
|
|
171
174
|
print(f"{PLUGIN_NAME}: Connected to Revit {instance.version} (pid={instance.process_id})")
|
|
172
175
|
return instance
|
|
173
176
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/constants.py
RENAMED
|
File without changes
|
|
File without changes
|
{revitdevtool_pytest-0.0.2 → revitdevtool_pytest-0.0.3}/src/revitdevtool_pytest/serializer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|