answer42 0.5.7__tar.gz → 0.5.8__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 (65) hide show
  1. {answer42-0.5.7 → answer42-0.5.8}/PKG-INFO +1 -1
  2. {answer42-0.5.7 → answer42-0.5.8}/pyproject.toml +1 -1
  3. {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form/Module.bsl +95 -3
  4. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/assets/MCPTestClient.cf +0 -0
  5. answer42-0.5.8/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  6. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/runtime.py +118 -15
  7. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/server.py +183 -92
  8. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/win_desktop.py +72 -11
  9. answer42-0.5.7/src/mcp_1c/assets/MCPTestManager.cf +0 -0
  10. {answer42-0.5.7 → answer42-0.5.8}/.gitignore +0 -0
  11. {answer42-0.5.7 → answer42-0.5.8}/LICENSE +0 -0
  12. {answer42-0.5.7 → answer42-0.5.8}/README.md +0 -0
  13. {answer42-0.5.7 → answer42-0.5.8}/credentials.example.json +0 -0
  14. {answer42-0.5.7 → answer42-0.5.8}/docs/agent-installation.md +0 -0
  15. {answer42-0.5.7 → answer42-0.5.8}/docs/architecture.md +0 -0
  16. {answer42-0.5.7 → answer42-0.5.8}/docs/assets/answer42-logo.png +0 -0
  17. {answer42-0.5.7 → answer42-0.5.8}/docs/assets/platform42-logo.svg +0 -0
  18. {answer42-0.5.7 → answer42-0.5.8}/docs/installation.md +0 -0
  19. {answer42-0.5.7 → answer42-0.5.8}/scripts/build_cf.py +0 -0
  20. {answer42-0.5.7 → answer42-0.5.8}/scripts/build_pages.py +0 -0
  21. {answer42-0.5.7 → answer42-0.5.8}/scripts/e2e_http_smoke.py +0 -0
  22. {answer42-0.5.7 → answer42-0.5.8}/scripts/e2e_stable.py +0 -0
  23. {answer42-0.5.7 → answer42-0.5.8}/scripts/openclaw_mcp_autoreload.py +0 -0
  24. {answer42-0.5.7 → answer42-0.5.8}/scripts/rag_cli.py +0 -0
  25. {answer42-0.5.7 → answer42-0.5.8}/scripts/read_webclient_start_info.py +0 -0
  26. {answer42-0.5.7 → answer42-0.5.8}/src/cf/ConfigDumpInfo.xml +0 -0
  27. {answer42-0.5.7 → answer42-0.5.8}/src/cf/Configuration.xml +0 -0
  28. {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager/Ext/ObjectModule.bsl +0 -0
  29. {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260/Ext/Form.xml" +0 -0
  30. {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager/Forms//320/244/320/276/321/200/320/274/320/260.xml" +0 -0
  31. {answer42-0.5.7 → answer42-0.5.8}/src/cf/DataProcessors/MCPTestManager.xml +0 -0
  32. {answer42-0.5.7 → answer42-0.5.8}/src/cf/Ext/ManagedApplicationModule.bsl +0 -0
  33. {answer42-0.5.7 → answer42-0.5.8}/src/cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  34. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Catalogs//320/237/320/241_/320/222/320/273/320/260/320/264/320/265/320/273/320/265/321/206.xml" +0 -0
  35. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Catalogs//320/237/320/241_/320/241/320/277/321/200/320/260/320/262/320/276/321/207/320/275/320/270/320/2721.xml" +0 -0
  36. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/ConfigDumpInfo.xml +0 -0
  37. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Configuration.xml +0 -0
  38. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Languages//320/240/321/203/321/201/321/201/320/272/320/270/320/271.xml" +0 -0
  39. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ManagerModule.bsl" +0 -0
  40. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Ext/ObjectModule.bsl" +0 -0
  41. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205/Ext/Template.xml" +0 -0
  42. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262/Templates//320/236/321/201/320/275/320/276/320/262/320/275/320/260/321/217/320/241/321/205/320/265/320/274/320/260/320/232/320/276/320/274/320/277/320/276/320/275/320/276/320/262/320/272/320/270/320/224/320/260/320/275/320/275/321/213/321/205.xml" +0 -0
  43. {answer42-0.5.7 → answer42-0.5.8}/src/client_cf/Reports//320/237/320/241_/320/241/320/277/320/270/321/201/320/276/320/272/320/255/320/273/320/265/320/274/320/265/320/275/321/202/320/276/320/262.xml" +0 -0
  44. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/__init__.py +0 -0
  45. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/assets/__init__.py +0 -0
  46. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/auth.py +0 -0
  47. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/bridge.py +0 -0
  48. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/credentials.py +0 -0
  49. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/os_support.py +0 -0
  50. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/platform.py +0 -0
  51. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/protocol.py +0 -0
  52. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/__init__.py +0 -0
  53. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/detect.py +0 -0
  54. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/dump.py +0 -0
  55. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/model.py +0 -0
  56. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/parsers.py +0 -0
  57. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/service.py +0 -0
  58. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/store.py +0 -0
  59. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/summary_backend.py +0 -0
  60. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/rag/summary_vector.py +0 -0
  61. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/recorder.py +0 -0
  62. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/release_helper.py +0 -0
  63. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/skill_installer.py +0 -0
  64. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/webclient_probe.py +0 -0
  65. {answer42-0.5.7 → answer42-0.5.8}/src/mcp_1c/window_control.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: answer42
3
- Version: 0.5.7
3
+ Version: 0.5.8
4
4
  Summary: Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API
5
5
  Author: Marvin (AI Assistant), 42Clouds, and contributors
6
6
  Author-email: "Kosolapov Stanislav (proDOOMman)" <prodoomman@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "answer42"
3
- version = "0.5.7"
3
+ version = "0.5.8"
4
4
  description = "Answer42 — The Answer to Life, Universe, and 1C — UI Driver. MCP-powered 1C:Enterprise UI automation: click, fill, navigate, test, and introspect managed forms through the test-client API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,5 +1,10 @@
1
1
  #Область ОписаниеПеременных
2
2
 
3
+ // Имена локальных переменных, совпадающие со свойствами управляемой формы
4
+ // (Заголовок, ТолькоПросмотр, ТекущийЭлемент и т.п.), без явного "Перем"
5
+ // ссылаются на свойства этой формы менеджера: присваивание меняет форму,
6
+ // а чтение возвращает её значение, а не параметр команды. Поэтому такие
7
+ // переменные объявляются в функциях через "Перем".
3
8
  &НаКлиенте
4
9
  Перем СоединениеWebSocket;
5
10
 
@@ -921,6 +926,7 @@
921
926
  // BSLLS:DeprecatedCurrentDate-off
922
927
  &НаКлиенте
923
928
  Процедура ПодключитьсяКТестовомуКлиенту(Пользователь = "", ИмяФайлаЛога = "")
929
+ Перем Подсказка;
924
930
  Лог("ПодключитьсяКТестовомуКлиенту: ждём 3с перед попыткой соединения");
925
931
  Пауза = ТекущаяДата() + 3;
926
932
  Пока ТекущаяДата() < Пауза Цикл
@@ -1075,6 +1081,7 @@
1075
1081
 
1076
1082
  &НаКлиенте
1077
1083
  Функция АктивироватьОкноMCP(Параметры)
1084
+ Перем Заголовок;
1078
1085
  Если ТестируемоеПриложение = Неопределено Тогда
1079
1086
  ВызватьИсключение "Тестируемое приложение не подключено.";
1080
1087
  КонецЕсли;
@@ -1103,11 +1110,35 @@
1103
1110
  ТекущийИндекс = ТекущийИндекс + 1;
1104
1111
  КонецЦикла;
1105
1112
 
1113
+ // Заголовок окна клиента часто не совпадает с заголовком открытой в нём
1114
+ // формы (главное окно, окно с несколькими формами), поэтому при промахе
1115
+ // по заголовку окна ищем окно, содержащее форму с таким заголовком.
1116
+ Если НайденноеОкно = Неопределено И Индекс < 0 И Заголовок <> "" Тогда
1117
+ ТекущийИндекс = 0;
1118
+ Для Каждого ТекущееОкно Из ВсеОкна Цикл
1119
+ Если НайтиОбъектРекурсивно(ТекущееОкно, "", Заголовок, "ТестируемаяФорма", 0) <> Неопределено Тогда
1120
+ НайденноеОкно = ТекущееОкно;
1121
+ НайденныйИндекс = ТекущийИндекс;
1122
+ Прервать;
1123
+ КонецЕсли;
1124
+ ТекущийИндекс = ТекущийИндекс + 1;
1125
+ КонецЦикла;
1126
+ КонецЕсли;
1127
+
1106
1128
  Если НайденноеОкно = Неопределено Тогда
1107
1129
  Возврат Новый Структура("activated,index,count,reason", Ложь, Индекс, ВсеОкна.Количество(), "window_not_found");
1108
1130
  КонецЕсли;
1109
1131
 
1110
- НайденноеОкно.Активизировать();
1132
+ Попытка
1133
+ НайденноеОкно.Активизировать();
1134
+ Исключение
1135
+ // Окно найдено, но недоступно: его блокирует модальная или
1136
+ // вспомогательная форма. Это состояние интерфейса, а не сбой моста.
1137
+ ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
1138
+ АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
1139
+ Результат = Новый Структура("activated,index,count,reason,error,window,active_window", Ложь, НайденныйИндекс, ВсеОкна.Количество(), "window_unavailable", ТекстОшибки, ОбъектВСтруктуру(НайденноеОкно, 0), КраткоеОписаниеОбъектаMCP(АктивноеОкно));
1140
+ Возврат Результат;
1141
+ КонецПопытки;
1111
1142
  Возврат Новый Структура("activated,index,count,window", Истина, НайденныйИндекс, ВсеОкна.Количество(), ОбъектВСтруктуру(НайденноеОкно, 0));
1112
1143
  КонецФункции
1113
1144
 
@@ -1133,6 +1164,7 @@
1133
1164
 
1134
1165
  &НаКлиенте
1135
1166
  Функция ОжидатьОбъектMCP(Параметры)
1167
+ Перем Заголовок;
1136
1168
  Если ТестируемоеПриложение = Неопределено Тогда
1137
1169
  ВызватьИсключение "Тестируемое приложение не подключено.";
1138
1170
  КонецЕсли;
@@ -1168,6 +1200,7 @@
1168
1200
 
1169
1201
  &НаКлиенте
1170
1202
  Функция ОжидатьФормуMCP(Параметры)
1203
+ Перем Заголовок;
1171
1204
  Если ТестируемоеПриложение = Неопределено Тогда
1172
1205
  ВызватьИсключение "Тестируемое приложение не подключено.";
1173
1206
  КонецЕсли;
@@ -1180,11 +1213,13 @@
1180
1213
  Если Заголовок <> "" Тогда ПараметрыПоиска.Вставить("Заголовок", Заголовок); КонецЕсли;
1181
1214
  ПараметрыПоиска.Вставить("Тип", "ТестируемаяФорма");
1182
1215
 
1183
- АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
1184
1216
  Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
1185
1217
  КонецОжидания = Начало + Таймаут * 1000;
1186
1218
  НайденнаяФорма = Неопределено;
1187
1219
  Пока ТекущаяУниверсальнаяДатаВМиллисекундах() <= КонецОжидания Цикл
1220
+ // Ожидаемая форма может открыться в новом окне: активное окно
1221
+ // перечитывается на каждой итерации.
1222
+ АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
1188
1223
  НайденнаяФорма = НайтиОбъектРекурсивно(АктивноеОкно, ЛокИмяФормы, Заголовок, "ТестируемаяФорма", 0);
1189
1224
  Если НайденнаяФорма <> Неопределено Тогда
1190
1225
  Прервать;
@@ -1202,6 +1237,7 @@
1202
1237
 
1203
1238
  &НаКлиенте
1204
1239
  Функция ОжидатьОкноMCP(Параметры)
1240
+ Перем Заголовок;
1205
1241
  Если ТестируемоеПриложение = Неопределено Тогда
1206
1242
  ВызватьИсключение "Тестируемое приложение не подключено.";
1207
1243
  КонецЕсли;
@@ -1438,6 +1474,7 @@
1438
1474
 
1439
1475
  &НаКлиенте
1440
1476
  Функция ПодсказкаПоляMCP(Параметры)
1477
+ Перем Подсказка;
1441
1478
  Если ТестируемоеПриложение = Неопределено Тогда
1442
1479
  ВызватьИсключение "Тестируемое приложение не подключено.";
1443
1480
  КонецЕсли;
@@ -1825,6 +1862,7 @@
1825
1862
 
1826
1863
  &НаКлиенте
1827
1864
  Функция КоманднаяПанельОбъектаMCP(Параметры)
1865
+ Перем Заголовок;
1828
1866
  Если ТестируемоеПриложение = Неопределено Тогда
1829
1867
  ВызватьИсключение "Тестируемое приложение не подключено.";
1830
1868
  КонецЕсли;
@@ -1858,6 +1896,7 @@
1858
1896
 
1859
1897
  &НаКлиенте
1860
1898
  Функция КонтекстноеМенюОбъектаMCP(Параметры)
1899
+ Перем Заголовок;
1861
1900
  Если ТестируемоеПриложение = Неопределено Тогда
1862
1901
  ВызватьИсключение "Тестируемое приложение не подключено.";
1863
1902
  КонецЕсли;
@@ -2281,6 +2320,7 @@
2281
2320
 
2282
2321
  &НаКлиенте
2283
2322
  Функция КомандныйИнтерфейсСлужебныйРазделMCP(ЗаголовокРаздела)
2323
+ Перем Заголовок;
2284
2324
  Заголовок = НРег(СокрЛП(ЗаголовокРаздела));
2285
2325
  Возврат Заголовок = "помощь" Или Заголовок = "help";
2286
2326
  КонецФункции
@@ -2330,6 +2370,7 @@
2330
2370
 
2331
2371
  &НаКлиенте
2332
2372
  Процедура КомандыКомандногоИнтерфейсаРекурсивно(Объект, Результат, Путь, ИсключатьСлужебныеПанели)
2373
+ Перем Заголовок;
2333
2374
  Если Объект = Неопределено Тогда
2334
2375
  Возврат;
2335
2376
  КонецЕсли;
@@ -2372,6 +2413,7 @@
2372
2413
 
2373
2414
  &НаКлиенте
2374
2415
  Функция ПоискВКомандахКомандногоИнтерфейса(Команды, СтрокаПоиска, Максимум)
2416
+ Перем Заголовок;
2375
2417
  Результат = Новый Массив;
2376
2418
  КлючиСовпадений = Новый Соответствие;
2377
2419
  НормПоиск = НРег(СтрокаПоиска);
@@ -2463,6 +2505,7 @@
2463
2505
 
2464
2506
  &НаКлиенте
2465
2507
  Функция РазвернутьГруппуMCP(Параметры)
2508
+ Перем Заголовок;
2466
2509
  Если ТестируемоеПриложение = Неопределено Тогда
2467
2510
  ВызватьИсключение "Тестируемое приложение не подключено.";
2468
2511
  КонецЕсли;
@@ -2489,6 +2532,7 @@
2489
2532
 
2490
2533
  &НаКлиенте
2491
2534
  Функция СвернутьГруппуMCP(Параметры)
2535
+ Перем Заголовок;
2492
2536
  Если ТестируемоеПриложение = Неопределено Тогда
2493
2537
  ВызватьИсключение "Тестируемое приложение не подключено.";
2494
2538
  КонецЕсли;
@@ -2515,6 +2559,7 @@
2515
2559
 
2516
2560
  &НаКлиенте
2517
2561
  Функция ТекущаяСтраницаГруппыMCP(Параметры)
2562
+ Перем Заголовок;
2518
2563
  Если ТестируемоеПриложение = Неопределено Тогда
2519
2564
  ВызватьИсключение "Тестируемое приложение не подключено.";
2520
2565
  КонецЕсли;
@@ -2545,6 +2590,7 @@
2545
2590
 
2546
2591
  &НаКлиенте
2547
2592
  Функция АктивироватьСтраницуГруппыMCP(Параметры)
2593
+ Перем Заголовок;
2548
2594
  Если ТестируемоеПриложение = Неопределено Тогда
2549
2595
  ВызватьИсключение "Тестируемое приложение не подключено.";
2550
2596
  КонецЕсли;
@@ -2574,6 +2620,7 @@
2574
2620
 
2575
2621
  &НаКлиенте
2576
2622
  Функция СостояниеГруппыMCP(Параметры)
2623
+ Перем Заголовок;
2577
2624
  Если ТестируемоеПриложение = Неопределено Тогда
2578
2625
  ВызватьИсключение "Тестируемое приложение не подключено.";
2579
2626
  КонецЕсли;
@@ -2605,6 +2652,7 @@
2605
2652
 
2606
2653
  &НаКлиенте
2607
2654
  Функция ГиперссылкиДекорацииMCP(Параметры)
2655
+ Перем Заголовок;
2608
2656
  Если ТестируемоеПриложение = Неопределено Тогда
2609
2657
  ВызватьИсключение "Тестируемое приложение не подключено.";
2610
2658
  КонецЕсли;
@@ -2635,6 +2683,7 @@
2635
2683
 
2636
2684
  &НаКлиенте
2637
2685
  Функция НажатьГиперссылкуДекорацииMCP(Параметры)
2686
+ Перем Заголовок;
2638
2687
  Если ТестируемоеПриложение = Неопределено Тогда
2639
2688
  ВызватьИсключение "Тестируемое приложение не подключено.";
2640
2689
  КонецЕсли;
@@ -2662,6 +2711,7 @@
2662
2711
 
2663
2712
  &НаКлиенте
2664
2713
  Функция НажатьДекорациюMCP(Параметры)
2714
+ Перем Заголовок;
2665
2715
  Если ТестируемоеПриложение = Неопределено Тогда
2666
2716
  ВызватьИсключение "Тестируемое приложение не подключено.";
2667
2717
  КонецЕсли;
@@ -2688,6 +2738,7 @@
2688
2738
 
2689
2739
  &НаКлиенте
2690
2740
  Функция ВыделитьВсеСтрокиТаблицыMCP(Параметры)
2741
+ Перем Заголовок;
2691
2742
  Если ТестируемоеПриложение = Неопределено Тогда
2692
2743
  ВызватьИсключение "Тестируемое приложение не подключено.";
2693
2744
  КонецЕсли;
@@ -2714,6 +2765,7 @@
2714
2765
 
2715
2766
  &НаКлиенте
2716
2767
  Функция СнятьВыделениеТаблицыMCP(Параметры)
2768
+ Перем Заголовок;
2717
2769
  Если ТестируемоеПриложение = Неопределено Тогда
2718
2770
  ВызватьИсключение "Тестируемое приложение не подключено.";
2719
2771
  КонецЕсли;
@@ -2740,6 +2792,7 @@
2740
2792
 
2741
2793
  &НаКлиенте
2742
2794
  Функция УдалитьСтрокуТаблицыMCP(Параметры)
2795
+ Перем Заголовок;
2743
2796
  Если ТестируемоеПриложение = Неопределено Тогда
2744
2797
  ВызватьИсключение "Тестируемое приложение не подключено.";
2745
2798
  КонецЕсли;
@@ -2766,6 +2819,7 @@
2766
2819
 
2767
2820
  &НаКлиенте
2768
2821
  Функция СкопироватьСтрокуТаблицыMCP(Параметры)
2822
+ Перем Заголовок;
2769
2823
  Если ТестируемоеПриложение = Неопределено Тогда
2770
2824
  ВызватьИсключение "Тестируемое приложение не подключено.";
2771
2825
  КонецЕсли;
@@ -2792,6 +2846,7 @@
2792
2846
 
2793
2847
  &НаКлиенте
2794
2848
  Функция ПереключитьПометкуУдаленияТаблицыMCP(Параметры)
2849
+ Перем Заголовок;
2795
2850
  Если ТестируемоеПриложение = Неопределено Тогда
2796
2851
  ВызватьИсключение "Тестируемое приложение не подключено.";
2797
2852
  КонецЕсли;
@@ -2818,6 +2873,7 @@
2818
2873
 
2819
2874
  &НаКлиенте
2820
2875
  Функция НачатьРедактированиеТаблицыMCP(Параметры)
2876
+ Перем Заголовок;
2821
2877
  Если ТестируемоеПриложение = Неопределено Тогда
2822
2878
  ВызватьИсключение "Тестируемое приложение не подключено.";
2823
2879
  КонецЕсли;
@@ -2852,6 +2908,7 @@
2852
2908
 
2853
2909
  &НаКлиенте
2854
2910
  Функция ЗакончитьРедактированиеТаблицыMCP(Параметры)
2911
+ Перем Заголовок;
2855
2912
  Если ТестируемоеПриложение = Неопределено Тогда
2856
2913
  ВызватьИсключение "Тестируемое приложение не подключено.";
2857
2914
  КонецЕсли;
@@ -2878,6 +2935,7 @@
2878
2935
 
2879
2936
  &НаКлиенте
2880
2937
  Функция РежимРедактированияТаблицыMCP(Параметры)
2938
+ Перем Заголовок;
2881
2939
  Если ТестируемоеПриложение = Неопределено Тогда
2882
2940
  ВызватьИсключение "Тестируемое приложение не подключено.";
2883
2941
  КонецЕсли;
@@ -2909,10 +2967,23 @@
2909
2967
  КонецЕсли;
2910
2968
  АктивноеОкно = ТестируемоеПриложение.ПолучитьАктивноеОкно();
2911
2969
  Ошибки = Новый Массив;
2970
+ // Окно уже активно; Активизировать() нужно только чтобы поднять его.
2971
+ // Пока открыта модальная форма (стартовые формы БСП, предупреждения),
2972
+ // главное окно недоступно и платформа отвечает ошибкой
2973
+ // "Недоступный пользователю элемент управления не может выполнять
2974
+ // интерактивные действия" - это штатное состояние, а не сбой старта.
2975
+ Доступно = БезопасноеЗначениеМетода(АктивноеОкно, "ТекущаяДоступность", БезопасноеСвойство(АктивноеОкно, "Доступность", Истина));
2976
+ Если Доступно = Ложь Тогда
2977
+ Возврат Новый Структура("maximized,method,errors,window,skipped", Истина, "activate_only", Ошибки, КраткоеОписаниеОбъектаMCP(АктивноеОкно), "window_unavailable");
2978
+ КонецЕсли;
2912
2979
  Попытка
2913
2980
  АктивноеОкно.Активизировать();
2914
2981
  Исключение
2915
- Ошибки.Добавить("Активизировать: " + ОписаниеОшибки());
2982
+ ТекстОшибки = ОписаниеОшибки();
2983
+ Если СтрНайти(НРег(ТекстОшибки), "недоступный пользователю элемент") > 0 Тогда
2984
+ Возврат Новый Структура("maximized,method,errors,window,skipped", Истина, "activate_only", Ошибки, КраткоеОписаниеОбъектаMCP(АктивноеОкно), "window_unavailable");
2985
+ КонецЕсли;
2986
+ Ошибки.Добавить("Активизировать: " + ТекстОшибки);
2916
2987
  КонецПопытки;
2917
2988
  Возврат Новый Структура("maximized,method,errors,window", Истина, "activate_only", Ошибки, КраткоеОписаниеОбъектаMCP(АктивноеОкно));
2918
2989
  КонецФункции
@@ -3033,6 +3104,7 @@
3033
3104
 
3034
3105
  &НаКлиенте
3035
3106
  Процедура НайтиУзлыДереваИнтерфейсаПлоскоMCP(Объект, Имя, Заголовок, ТипОбъекта, ТолькоКомандныеПанели, ПоказыватьСкрытые, ВключатьДанные, ВключатьКомандныеПанели, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации, СчетчикУзлов, Найденные, ТекущаяГлубина, ВнутриКоманднойПанели)
3107
+ Перем Видимость;
3036
3108
  Если Объект = Неопределено Тогда
3037
3109
  Возврат;
3038
3110
  КонецЕсли;
@@ -3074,6 +3146,7 @@
3074
3146
 
3075
3147
  &НаКлиенте
3076
3148
  Функция НайтиУзлыДереваИнтерфейсаБыстроMCP(КорневойОбъект, Имя, ТипОбъектаСтрокой, ПоказыватьСкрытые, ВключатьДанные, ВключатьСостояниеВидимости, ВключатьСостояниеДоступности, ВключатьСостояниеТолькоПросмотр, ВключатьТекущееСостояние, КешСериализации)
3149
+ Перем Видимость;
3077
3150
  ТипОбъекта = ТипТестируемогоОбъектаMCP(ТипОбъектаСтрокой);
3078
3151
  Если Не ПустаяСтрока(ТипОбъектаСтрокой) И ТипОбъекта = Неопределено Тогда
3079
3152
  Возврат Неопределено;
@@ -3153,6 +3226,7 @@
3153
3226
 
3154
3227
  &НаКлиенте
3155
3228
  Функция НайтиОбъектИнтерфейса(Параметры)
3229
+ Перем Заголовок;
3156
3230
  Если ТестируемоеПриложение = Неопределено Тогда
3157
3231
  ВызватьИсключение "Тестируемое приложение не подключено.";
3158
3232
  КонецЕсли;
@@ -3327,6 +3401,7 @@
3327
3401
 
3328
3402
  &НаКлиенте
3329
3403
  Функция ОбъектИнтерактивноДоступенMCP(Объект)
3404
+ Перем ТолькоПросмотр, Доступность, Видимость;
3330
3405
  Видимость = БезопасноеЗначениеМетода(Объект, "ТекущаяВидимость", БезопасноеСвойство(Объект, "Видимость", Истина));
3331
3406
  Доступность = БезопасноеЗначениеМетода(Объект, "ТекущаяДоступность", БезопасноеСвойство(Объект, "Доступность", Истина));
3332
3407
  Если Не Видимость Или Не Доступность Тогда
@@ -3342,6 +3417,7 @@
3342
3417
 
3343
3418
  &НаКлиенте
3344
3419
  Функция ОбъектВидимИДоступенMCP(Объект)
3420
+ Перем Доступность, Видимость;
3345
3421
  Видимость = БезопасноеЗначениеМетода(Объект, "ТекущаяВидимость", БезопасноеСвойство(Объект, "Видимость", Истина));
3346
3422
  Доступность = БезопасноеЗначениеМетода(Объект, "ТекущаяДоступность", БезопасноеСвойство(Объект, "Доступность", Истина));
3347
3423
  Возврат Видимость И Доступность;
@@ -3349,6 +3425,7 @@
3349
3425
 
3350
3426
  &НаКлиенте
3351
3427
  Функция НайтиВсеОбъектыИнтерфейса(Параметры)
3428
+ Перем Заголовок;
3352
3429
  Если ТестируемоеПриложение = Неопределено Тогда
3353
3430
  ВызватьИсключение "Тестируемое приложение не подключено.";
3354
3431
  КонецЕсли;
@@ -3612,6 +3689,7 @@
3612
3689
 
3613
3690
  &НаКлиенте
3614
3691
  Функция БезопасныйЗаголовок(Объект)
3692
+ Перем Заголовок;
3615
3693
  Заголовок = БезопасноеСвойство(Объект, "Заголовок", "");
3616
3694
  Если ПустаяСтрока(Заголовок) Тогда
3617
3695
  Заголовок = БезопасноеСвойство(Объект, "ТекстЗаголовка", "");
@@ -3735,6 +3813,7 @@
3735
3813
 
3736
3814
  &НаКлиенте
3737
3815
  Функция БезопасныйЗаголовокСКешемMCP(Объект, КешПоиска)
3816
+ Перем Заголовок;
3738
3817
  Заголовок = БезопасноеСвойствоСКешемMCP(Объект, "Заголовок", "", КешПоиска);
3739
3818
  Если ПустаяСтрока(Заголовок) Тогда
3740
3819
  Заголовок = БезопасноеСвойствоСКешемMCP(Объект, "ТекстЗаголовка", "", КешПоиска);
@@ -3778,6 +3857,7 @@
3778
3857
 
3779
3858
  &НаКлиенте
3780
3859
  Функция ИмяТекущейСтраницыСКешемMCP(Объект, КешПоиска)
3860
+ Перем ТекущаяСтраница;
3781
3861
  Если КешПоиска <> Неопределено Тогда
3782
3862
  Запись = КешПоиска.ТекущиеСтраницы.Получить(Объект);
3783
3863
  Если Запись <> Неопределено Тогда
@@ -3959,6 +4039,7 @@
3959
4039
 
3960
4040
  &НаКлиенте
3961
4041
  Функция ПолучитьОбъектПриложенияMCP(Параметры)
4042
+ Перем Заголовок;
3962
4043
  Если ТестируемоеПриложение = Неопределено Тогда
3963
4044
  ВызватьИсключение "Тестируемое приложение не подключено.";
3964
4045
  КонецЕсли;
@@ -3984,6 +4065,7 @@
3984
4065
 
3985
4066
  &НаКлиенте
3986
4067
  Функция ФокусироватьОбъектИнтерфейса(Параметры)
4068
+ Перем Заголовок;
3987
4069
  Если ТестируемоеПриложение = Неопределено Тогда
3988
4070
  ВызватьИсключение "Тестируемое приложение не подключено.";
3989
4071
  КонецЕсли;
@@ -4310,6 +4392,7 @@
4310
4392
 
4311
4393
  &НаКлиенте
4312
4394
  Функция ТекстТабличногоДокумента(Параметры)
4395
+ Перем Заголовок;
4313
4396
  Если ТестируемоеПриложение = Неопределено Тогда
4314
4397
  ВызватьИсключение "Тестируемое приложение не подключено.";
4315
4398
  КонецЕсли;
@@ -4394,6 +4477,7 @@
4394
4477
 
4395
4478
  &НаКлиенте
4396
4479
  Функция ОтображениеСостоянияТабличногоДокументаMCP(Параметры)
4480
+ Перем Заголовок;
4397
4481
  Если ТестируемоеПриложение = Неопределено Тогда
4398
4482
  ВызватьИсключение "Тестируемое приложение не подключено.";
4399
4483
  КонецЕсли;
@@ -4443,6 +4527,7 @@
4443
4527
 
4444
4528
  &НаКлиенте
4445
4529
  Функция СохранитьТабличныйДокументMCP(Параметры)
4530
+ Перем Заголовок;
4446
4531
  Если ТестируемоеПриложение = Неопределено Тогда
4447
4532
  ВызватьИсключение "Тестируемое приложение не подключено.";
4448
4533
  КонецЕсли;
@@ -5710,6 +5795,7 @@
5710
5795
 
5711
5796
  &НаКлиенте
5712
5797
  Функция ПроверитьАктивностьСтраницыРодителя(Объект)
5798
+ Перем ТекущаяСтраница;
5713
5799
  Страница = НайтиРодительскуюСтраницу(Объект);
5714
5800
  Если Страница = Неопределено Тогда
5715
5801
  Возврат Неопределено;
@@ -6295,6 +6381,7 @@
6295
6381
 
6296
6382
  &НаКлиенте
6297
6383
  Функция НайтиПолеФлажкаТекущейСтрокиТаблицыMCP(ИмяТаблицы, ИмяФлажка, ИскатьПоЗаголовку, Таблица = Неопределено)
6384
+ Перем ТекущийЭлемент;
6298
6385
  Если Таблица <> Неопределено Тогда
6299
6386
  Попытка
6300
6387
  ТекущийЭлемент = Таблица.ПолучитьТекущийЭлемент();
@@ -6889,6 +6976,7 @@
6889
6976
 
6890
6977
  &НаКлиенте
6891
6978
  Функция НайтиВидимыйРедакторПоляНастройкиСпискаMCP(РодительскийОбъект, ИмяПоля, Глубина = 0)
6979
+ Перем ТолькоПросмотр;
6892
6980
  Если РодительскийОбъект = Неопределено Или Глубина > 30 Тогда
6893
6981
  Возврат Неопределено;
6894
6982
  КонецЕсли;
@@ -7104,6 +7192,7 @@
7104
7192
 
7105
7193
  &НаКлиенте
7106
7194
  Функция ВидимыйРедакторТаблицыДоступенMCP(Поле)
7195
+ Перем ТолькоПросмотр;
7107
7196
  Если Поле = Неопределено Тогда
7108
7197
  Возврат Ложь;
7109
7198
  КонецЕсли;
@@ -10060,6 +10149,7 @@
10060
10149
 
10061
10150
  &НаКлиенте
10062
10151
  Процедура ДобавитьСообщениеВалидацииЕслиЕстьMCP(Сообщения, Объект, Текст)
10152
+ Перем Заголовок;
10063
10153
  Текст = СокрЛП(Строка(Текст));
10064
10154
  Если Не ПохожеНаСообщениеВалидацииMCP(Текст) Тогда
10065
10155
  Возврат;
@@ -10078,6 +10168,7 @@
10078
10168
 
10079
10169
  &НаКлиенте
10080
10170
  Процедура ДобавитьСообщенияВалидацииТекущегоЭлементаMCP(АктивноеОкно, Сообщения)
10171
+ Перем ТекущийЭлемент;
10081
10172
  // Не сканируем все окно: ПолучитьТекстПодсказки() на большом дереве форм может быть дорогим.
10082
10173
  // После ошибки ввода/проведения платформа обычно переводит фокус на проблемный элемент.
10083
10174
  Попытка
@@ -10168,6 +10259,7 @@
10168
10259
 
10169
10260
  &НаКлиенте
10170
10261
  Функция ОбъектВСтруктуру(Объект, ТекущаяГлубина, ПоказыватьСкрытые = Истина, РодительскаяВидимость = Истина, РодительскаяДоступность = Истина, РодительТолькоПросмотр = Ложь, КешСериализации = Неопределено, ВключатьДанные = Истина, СериализоватьДетей = Истина, СчетчикУзлов = Неопределено, ВключатьКомандныеПанели = Истина, ВключатьСостояниеВидимости = Истина, ВключатьСостояниеДоступности = Истина, ВключатьСостояниеТолькоПросмотр = Истина, ВключатьТекущееСостояние = Истина)
10262
+ Перем ТолькоПросмотр, Доступность, Видимость;
10171
10263
  Если Объект = Неопределено Тогда
10172
10264
  Возврат Неопределено;
10173
10265
  КонецЕсли;
@@ -105,16 +105,30 @@ def find_free_display(start: int = DEFAULT_DISPLAY_START) -> str:
105
105
  raise RuntimeError("No free X display found")
106
106
 
107
107
 
108
+ def _port_bindable(port: int) -> bool:
109
+ """Return True when *port* can be bound both on all interfaces and on loopback.
110
+
111
+ On Windows a wildcard bind succeeds even while another socket listens on
112
+ ``127.0.0.1:port`` (no SO_EXCLUSIVEADDRUSE), and the later loopback bind of
113
+ the bridge, ibsrv or test client then fails with WinError 10048.
114
+ """
115
+ for host in ("", "127.0.0.1"):
116
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
117
+ if os_support.IS_WINDOWS and hasattr(socket, "SO_EXCLUSIVEADDRUSE"):
118
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
119
+ try:
120
+ s.bind((host, port))
121
+ except OSError:
122
+ return False
123
+ return True
124
+
125
+
108
126
  def find_free_port(start: int = DEFAULT_WS_PORT, end: int | None = None) -> int:
109
127
  """Find a free TCP port in the range start..start+500 (or start..end)."""
110
128
  end = end or start + 500
111
129
  for port in range(start, end):
112
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
113
- try:
114
- s.bind(("", port))
115
- return port
116
- except OSError:
117
- continue
130
+ if _port_bindable(port):
131
+ return port
118
132
  raise RuntimeError(f"No free port found in range {start}..{end}")
119
133
 
120
134
 
@@ -238,14 +252,33 @@ def kill_process(proc: subprocess.Popen | None, label: str) -> None:
238
252
  os_support.kill_process_tree(proc, label, LOGGER)
239
253
 
240
254
 
255
+ def _cyrillic_score(text: str) -> int:
256
+ score = 0
257
+ for char in text:
258
+ if ord(char) < 128:
259
+ continue
260
+ score += 1 if ("А" <= char <= "я" or char in "Ёё") else -1
261
+ return score
262
+
263
+
264
+ def decode_8bit_cyrillic(data: bytes) -> str:
265
+ """Decode single-byte Russian text written in either cp866 (OEM) or cp1251 (ANSI).
266
+
267
+ Windows ibsrv/ibcmd console output is OEM (cp866), while 1C /Out logs are
268
+ usually ANSI. Both decodings always succeed, so pick the one that yields
269
+ Cyrillic letters instead of pseudographics/punctuation.
270
+ """
271
+ candidates = [data.decode(encoding, errors="replace") for encoding in ("cp1251", "cp866")]
272
+ return max(candidates, key=_cyrillic_score)
273
+
274
+
241
275
  def _decode_process_output(data: bytes) -> str:
242
- encodings = ["utf-8-sig", "utf-8", "cp866", "cp1251"] if os_support.IS_WINDOWS else ["utf-8-sig", "utf-8", "cp866"]
243
- for encoding in encodings:
276
+ for encoding in ("utf-8-sig", "utf-8"):
244
277
  try:
245
278
  return data.decode(encoding)
246
279
  except UnicodeDecodeError:
247
280
  continue
248
- return data.decode(encodings[-1], errors="replace")
281
+ return decode_8bit_cyrillic(data)
249
282
 
250
283
 
251
284
  def _run_capture(argv: list[str], *, timeout: float, input_text: str | None = None) -> subprocess.CompletedProcess[str]:
@@ -397,6 +430,7 @@ def start_standalone_server(
397
430
  data_dir.mkdir(parents=True, exist_ok=True)
398
431
  log_dir.mkdir(parents=True, exist_ok=True)
399
432
  log_path = log_dir / "ibsrv.log"
433
+ log_offset = log_path.stat().st_size if log_path.exists() else 0
400
434
  with log_path.open("ab", buffering=0) as log_handle:
401
435
  LOGGER.info("Starting ibsrv (data=%s, name=%s, address=%s, port=%s, direct_regport=%s)", data_dir, name, address, port, direct_regport or "default")
402
436
  config_path = ensure_standalone_config(
@@ -423,16 +457,85 @@ def start_standalone_server(
423
457
  stdin=subprocess.DEVNULL,
424
458
  **os_support.subprocess_creation_kwargs(),
425
459
  )
426
- if not wait_for_port(address, port, timeout=timeout):
427
- kill_process(proc, "ibsrv")
428
- raise RuntimeError(
429
- f"ibsrv did not start listening on {address}:{port} within {timeout}s. "
430
- f"See log: {log_path}"
431
- )
460
+ _wait_standalone_server_ready(proc, address, port, timeout, log_path, log_offset, db_path=db_path)
432
461
  LOGGER.info("ibsrv is listening on %s:%s (pid=%s, name=%s)", address, port, proc.pid, name)
433
462
  return proc
434
463
 
435
464
 
465
+ _IBSRV_FATAL_MARKERS = ("[fatal]", "[error]")
466
+ _IBSRV_READY_MARKER = "server ready"
467
+
468
+
469
+ def read_log_since(path: Path, offset: int = 0, *, max_chars: int = 8000) -> str:
470
+ """Return text appended to *path* after byte *offset* (ibsrv.log is shared across starts)."""
471
+ try:
472
+ with path.open("rb") as handle:
473
+ handle.seek(max(0, offset))
474
+ data = handle.read()
475
+ except OSError:
476
+ return ""
477
+ text = _decode_process_output(data).strip()
478
+ return text[-max_chars:]
479
+
480
+
481
+ def _standalone_server_failure(proc: subprocess.Popen, output: str) -> str:
482
+ if proc.poll() is not None:
483
+ return f"ibsrv exited with code {proc.returncode}"
484
+ if any(marker in line.lower() for line in output.splitlines() for marker in _IBSRV_FATAL_MARKERS):
485
+ return "ibsrv reported a fatal error"
486
+ return ""
487
+
488
+
489
+ def _wait_standalone_server_ready(
490
+ proc: subprocess.Popen,
491
+ address: str,
492
+ port: int,
493
+ timeout: float,
494
+ log_path: Path,
495
+ log_offset: int,
496
+ db_path: Path | None = None,
497
+ ready_grace: float = 10.0,
498
+ poll: float = 0.25,
499
+ ) -> None:
500
+ """Wait until ibsrv serves HTTP and has opened the infobase.
501
+
502
+ ibsrv opens its HTTP port *before* it locks the infobase, so a listening
503
+ port alone is not enough: with a file infobase already opened elsewhere
504
+ (Designer, a web publication in Apache) it prints ``[FATAL] Ошибка
505
+ исключительной блокировки информационной базы`` and exits a moment later.
506
+ """
507
+ def fail(reason: str) -> None:
508
+ kill_process(proc, "ibsrv")
509
+ output = read_log_since(log_path, log_offset)
510
+ message = f"ibsrv failed to start on {address}:{port}: {reason}. Log: {log_path}"
511
+ if output:
512
+ message += f"\n{output}"
513
+ if "исключительной блокировки" in output.lower() or "exclusive lock" in output.lower():
514
+ message += (
515
+ f"\nИнформационная база {db_path} уже открыта другим процессом (Конфигуратор, "
516
+ "клиент 1С, веб-публикация в Apache/IIS, другой ibsrv). Закройте его и повторите."
517
+ )
518
+ raise RuntimeError(message)
519
+
520
+ deadline = time.monotonic() + timeout
521
+ while not is_port_listening(address, port):
522
+ reason = _standalone_server_failure(proc, read_log_since(log_path, log_offset))
523
+ if reason:
524
+ fail(reason)
525
+ if time.monotonic() >= deadline:
526
+ fail(f"did not start listening within {timeout}s")
527
+ time.sleep(poll)
528
+ ready_deadline = time.monotonic() + ready_grace
529
+ while time.monotonic() < ready_deadline:
530
+ output = read_log_since(log_path, log_offset)
531
+ reason = _standalone_server_failure(proc, output)
532
+ if reason:
533
+ fail(reason)
534
+ if _IBSRV_READY_MARKER in output.lower():
535
+ return
536
+ time.sleep(poll)
537
+
538
+
436
539
  def export_eventlog(
437
540
  ibcmd: str,
438
541
  eventlog_dir: Path,