chrometools-mcp 2.4.2 → 3.1.2

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 (48) hide show
  1. package/CHANGELOG.md +540 -0
  2. package/COMPONENT_MAPPING_SPEC.md +1217 -0
  3. package/README.md +494 -38
  4. package/bridge/bridge-client.js +472 -0
  5. package/bridge/bridge-service.js +399 -0
  6. package/bridge/install.js +241 -0
  7. package/browser/browser-manager.js +107 -2
  8. package/browser/page-manager.js +226 -69
  9. package/docs/CHROME_EXTENSION.md +219 -0
  10. package/docs/PAGE_OBJECT_MODEL_CONCEPT.md +1756 -0
  11. package/element-finder-utils.js +138 -28
  12. package/extension/background.js +643 -0
  13. package/extension/content.js +715 -0
  14. package/extension/icons/create-icons.js +164 -0
  15. package/extension/icons/icon128.png +0 -0
  16. package/extension/icons/icon16.png +0 -0
  17. package/extension/icons/icon48.png +0 -0
  18. package/extension/manifest.json +58 -0
  19. package/extension/popup/popup.css +437 -0
  20. package/extension/popup/popup.html +102 -0
  21. package/extension/popup/popup.js +415 -0
  22. package/extension/recorder-overlay.css +93 -0
  23. package/figma-tools.js +120 -0
  24. package/index.js +3347 -2518
  25. package/models/BaseInputModel.js +93 -0
  26. package/models/CheckboxGroupModel.js +199 -0
  27. package/models/CheckboxModel.js +103 -0
  28. package/models/ColorInputModel.js +53 -0
  29. package/models/DateInputModel.js +67 -0
  30. package/models/RadioGroupModel.js +126 -0
  31. package/models/RangeInputModel.js +60 -0
  32. package/models/SelectModel.js +97 -0
  33. package/models/TextInputModel.js +34 -0
  34. package/models/TextareaModel.js +59 -0
  35. package/models/TimeInputModel.js +49 -0
  36. package/models/index.js +122 -0
  37. package/package.json +3 -2
  38. package/pom/apom-converter.js +267 -0
  39. package/pom/apom-tree-converter.js +515 -0
  40. package/pom/element-id-generator.js +175 -0
  41. package/recorder/page-object-generator.js +16 -0
  42. package/recorder/scenario-executor.js +80 -2
  43. package/server/tool-definitions.js +839 -656
  44. package/server/tool-groups.js +3 -2
  45. package/server/tool-schemas.js +367 -296
  46. package/server/websocket-bridge.js +447 -0
  47. package/utils/selector-resolver.js +186 -0
  48. package/utils/ui-framework-detector.js +392 -0
@@ -0,0 +1,1756 @@
1
+ # Agent Page Object Model (APOM) API - Концепция и спецификация
2
+
3
+ > **ВАЖНО: Терминология**
4
+ >
5
+ > В chrometools-mcp существуют **два разных** инструмента с похожими названиями, но **разным назначением**:
6
+ >
7
+ > 1. **Test Page Object (generatePageObject)** - существующий инструмент
8
+ > - **Назначение**: Экспорт структуры страницы в автотесты (Playwright/Selenium)
9
+ > - **Для кого**: QA-инженеры, разработчики автотестов
10
+ > - **Формат**: Генерация кода классов для test frameworks
11
+ > - **Файл**: `recorder/page-object-generator.js`
12
+ > - **Сохраняется**: ✅ Остаётся без изменений
13
+ >
14
+ > 2. **Agent Page Object Model (APOM)** - новый API (эта спецификация)
15
+ > - **Назначение**: Промежуточная модель коммуникации AI-агента с MCP chrometools
16
+ > - **Для кого**: AI-агенты (Claude, ChatGPT, etc.)
17
+ > - **Формат**: JSON-модель страницы с element IDs и actions
18
+ > - **Цель**: Упростить взаимодействие агента с браузером через объектную модель
19
+ > - **Новые инструменты**: `getPageObject`, `performAction`, `updatePageObject`, `queryElements`
20
+ >
21
+ > **Терминология в этом документе:**
22
+ > - **APOM (Agent Page Object Model)** = модель для AI-агентов
23
+ > - **Test Page Object** = существующий инструмент для автотестов
24
+
25
+ ## Содержание
26
+
27
+ 1. [Общая концепция](#общая-концепция)
28
+ 2. [Архитектура решения](#архитектура-решения)
29
+ 3. [Модели элементов](#модели-элементов)
30
+ 4. [API инструментов](#api-инструментов)
31
+ 5. [План разработки](#план-разработки)
32
+ 6. [Примеры использования](#примеры-использования)
33
+
34
+ ---
35
+
36
+ ## Общая концепция
37
+
38
+ ### Проблема
39
+
40
+ Текущая архитектура chrometools-mcp требует от AI агента работать с низкоуровневыми селекторами и отдельными командами для каждого действия. Это приводит к:
41
+
42
+ 1. **Множественным запросам**: для получения информации о странице и последующих действий
43
+ 2. **Потере контекста**: между вызовами инструментов элементы могут измениться
44
+ 3. **Ограниченной семантике**: агент не знает, какие операции доступны для конкретного элемента
45
+ 4. **Избыточным токенам**: получение HTML для простых операций
46
+
47
+ ### Решение: Agent Page Object Model (APOM) API
48
+
49
+ Предлагается создать объектную модель для AI-агентов, где:
50
+
51
+ 1. **Один инструмент возвращает полную модель страницы** с уникальными идентификаторами элементов
52
+ 2. **Каждый элемент содержит метаданные** о доступных операциях (actions)
53
+ 3. **Последующие команды работают с ID элементов**, а не с селекторами
54
+ 4. **Модели типизированы** в зависимости от типа элемента (input, form, button, link и т.д.)
55
+ 5. **ID элементов валидны в течение сессии** страницы (до перезагрузки/навигации)
56
+
57
+ ---
58
+
59
+ ## Архитектура решения
60
+
61
+ ### 1. Инструменты верхнего уровня
62
+
63
+ > **🔄 ВАЖНОЕ АРХИТЕКТУРНОЕ РЕШЕНИЕ (v3.0.0)**
64
+ >
65
+ > **analyzePage → getPageObject (переименование + расширение)**
66
+ >
67
+ > В v3.0.0 существующий инструмент `analyzePage` будет переименован и расширен:
68
+ > - **Старое название:** `analyzePage` (v1.0 - v2.x)
69
+ > - **Новое название:** `getPageObject` (v3.0.0+)
70
+ >
71
+ > **Что изменится:**
72
+ > - ✅ Автоматическая генерация уникальных ID для каждого элемента
73
+ > - ✅ Автоматическая регистрация элементов в реестре (использует `utils/selector-resolver.js`)
74
+ > - ✅ Возвращает `pageId` для валидации
75
+ > - ✅ Группировка элементов по типу/секциям
76
+ > - ✅ Сохранение всех существующих возможностей `analyzePage`
77
+ >
78
+ > **Обратная совместимость:**
79
+ > - Параметр `legacy: true` вернёт старый формат без ID (для миграции)
80
+ > - `analyzePage` будет работать как алиас для `getPageObject({ legacy: true })`
81
+ >
82
+ > **Миграция:**
83
+ > ```javascript
84
+ > // Старый код (v2.x):
85
+ > const analysis = analyzePage()
86
+ > click({ selector: "#email" })
87
+ >
88
+ > // Новый код (v3.0.0):
89
+ > const page = getPageObject()
90
+ > click({ selector: "input_email_0" }) // или "#email" (backward compatible)
91
+ > ```
92
+
93
+ #### `getPageObject` - получение объектной модели страницы (APOM)
94
+
95
+ **Статус:** 🚧 Планируется в v3.0.0 (переименование + расширение `analyzePage`)
96
+
97
+ **Назначение:** Главный инструмент APOM API — получение полной модели страницы с автоматической генерацией ID и регистрацией элементов
98
+
99
+ **Параметры:**
100
+ ```typescript
101
+ {
102
+ // Существующие параметры из analyzePage:
103
+ includeAll?: boolean, // Включить все видимые элементы (default: false)
104
+
105
+ // Новые параметры APOM:
106
+ refresh?: boolean, // Пересчитать модель (default: false)
107
+ generateIds?: boolean, // Генерировать уникальные ID (default: true в v3.0.0)
108
+ registerElements?: boolean, // Автоматически регистрировать элементы (default: true)
109
+ groupBy?: 'type' | 'section' | 'flat', // Группировка элементов (default: 'type')
110
+ maxElements?: number, // Лимит элементов (default: 200)
111
+
112
+ // Обратная совместимость:
113
+ legacy?: boolean // Вернуть старый формат analyzePage (default: false)
114
+ }
115
+ ```
116
+
117
+ **Возвращает:**
118
+ ```typescript
119
+ {
120
+ pageId: string, // Уникальный ID страницы (для валидации)
121
+ url: string,
122
+ title: string,
123
+ timestamp: number, // Когда создана модель
124
+
125
+ elements: {
126
+ [elementId: string]: PageElement // Карта элементов
127
+ },
128
+
129
+ // Группировки для удобства
130
+ groups: {
131
+ inputs?: ElementGroup,
132
+ buttons?: ElementGroup,
133
+ links?: ElementGroup,
134
+ forms?: FormGroup[],
135
+ sections?: SectionGroup[]
136
+ },
137
+
138
+ metadata: {
139
+ totalElements: number,
140
+ interactiveCount: number,
141
+ formCount: number
142
+ }
143
+ }
144
+ ```
145
+
146
+ #### `performAction` - выполнение действия над элементом 🚧 **НЕ РЕАЛИЗОВАН**
147
+
148
+ **Параметры:**
149
+ ```typescript
150
+ {
151
+ pageId: string, // Валидация что страница не изменилась
152
+ elementId: string, // ID элемента из getPageObject
153
+ action: ActionType, // Тип действия (зависит от элемента)
154
+ params?: ActionParams, // Параметры действия
155
+ screenshot?: boolean // Сделать скриншот после действия
156
+ }
157
+ ```
158
+
159
+ **Примеры actions:**
160
+ ```typescript
161
+ // Для input элемента
162
+ { action: 'type', params: { text: 'username' } }
163
+ { action: 'clear' }
164
+ { action: 'focus' }
165
+
166
+ // Для button
167
+ { action: 'click' }
168
+ { action: 'hover' }
169
+
170
+ // Для любого элемента
171
+ { action: 'setStyles', params: { styles: [{name: 'color', value: 'red'}] } }
172
+ { action: 'scrollTo' }
173
+
174
+ // Для select
175
+ { action: 'selectOption', params: { value: 'option1' } }
176
+
177
+ // Для form
178
+ { action: 'submit' }
179
+ { action: 'fillForm', params: { fields: {...} } }
180
+ ```
181
+
182
+ #### `updatePageObject` - обновление части модели 🚧 **НЕ РЕАЛИЗОВАН**
183
+
184
+ **Параметры:**
185
+ ```typescript
186
+ {
187
+ pageId: string,
188
+ elementIds?: string[], // Обновить только эти элементы (или все)
189
+ includeNew?: boolean // Добавить новые элементы (default: true)
190
+ }
191
+ ```
192
+
193
+ **Использование:** после динамических изменений на странице (AJAX, React re-renders)
194
+
195
+ #### `queryElements` - поиск элементов в модели 🚧 **НЕ РЕАЛИЗОВАН**
196
+
197
+ **Параметры:**
198
+ ```typescript
199
+ {
200
+ pageId: string,
201
+ query: {
202
+ type?: ElementType | ElementType[],
203
+ text?: string, // Поиск по тексту (substring, case-insensitive)
204
+ attributes?: Record<string, string>, // Поиск по атрибутам
205
+ visible?: boolean,
206
+ inForm?: boolean,
207
+ parentId?: string // Дочерние элементы
208
+ },
209
+ limit?: number // default: 20
210
+ }
211
+ ```
212
+
213
+ **Возвращает:** массив `ElementId[]`
214
+
215
+ ---
216
+
217
+ ### 2. Модели элементов
218
+
219
+ Базовая модель для всех элементов:
220
+
221
+ ```typescript
222
+ interface PageElement {
223
+ id: string, // Уникальный ID в рамках модели
224
+ type: ElementType, // Тип элемента
225
+ selector: string, // CSS селектор для Puppeteer
226
+
227
+ // Метаданные
228
+ tagName: string,
229
+ text?: string,
230
+ visible: boolean,
231
+ enabled: boolean,
232
+
233
+ // Геометрия
234
+ bounds?: {
235
+ x: number,
236
+ y: number,
237
+ width: number,
238
+ height: number
239
+ },
240
+
241
+ // Атрибуты
242
+ attributes: Record<string, string>,
243
+
244
+ // Доступные действия
245
+ actions: Action[],
246
+
247
+ // Стили (опционально при includeStyles: true)
248
+ styles?: Record<string, string>,
249
+
250
+ // Родитель/дети
251
+ parentId?: string,
252
+ childIds?: string[]
253
+ }
254
+ ```
255
+
256
+ #### 2.1. Input элементы
257
+
258
+ ```typescript
259
+ interface InputElement extends PageElement {
260
+ type: 'input',
261
+
262
+ inputType: 'text' | 'password' | 'email' | 'number' | 'tel' | 'search' | 'url' | 'date' | ...,
263
+ value: string,
264
+ placeholder?: string,
265
+ required: boolean,
266
+ disabled: boolean,
267
+ readonly: boolean,
268
+ maxLength?: number,
269
+ pattern?: string,
270
+
271
+ // Валидация
272
+ validation: {
273
+ required: boolean,
274
+ pattern?: string,
275
+ minLength?: number,
276
+ maxLength?: number,
277
+ min?: number, // для type=number/date
278
+ max?: number,
279
+ step?: number
280
+ },
281
+
282
+ // Состояние валидации
283
+ validationState?: {
284
+ valid: boolean,
285
+ message?: string
286
+ },
287
+
288
+ // Доступные действия
289
+ actions: [
290
+ { type: 'type', params: { text: string, delay?: number, clearFirst?: boolean } },
291
+ { type: 'clear' },
292
+ { type: 'focus' },
293
+ { type: 'blur' },
294
+ { type: 'setStyles', params: { styles: StylePair[] } },
295
+ { type: 'scrollTo' }
296
+ ]
297
+ }
298
+ ```
299
+
300
+ #### 2.2. Textarea элементы
301
+
302
+ ```typescript
303
+ interface TextareaElement extends PageElement {
304
+ type: 'textarea',
305
+
306
+ value: string,
307
+ placeholder?: string,
308
+ required: boolean,
309
+ disabled: boolean,
310
+ readonly: boolean,
311
+ maxLength?: number,
312
+ rows?: number,
313
+ cols?: number,
314
+
315
+ validation: {
316
+ required: boolean,
317
+ minLength?: number,
318
+ maxLength?: number
319
+ },
320
+
321
+ actions: [
322
+ { type: 'type', params: { text: string, clearFirst?: boolean } },
323
+ { type: 'clear' },
324
+ { type: 'focus' },
325
+ { type: 'setStyles', params: { styles: StylePair[] } },
326
+ { type: 'scrollTo' }
327
+ ]
328
+ }
329
+ ```
330
+
331
+ #### 2.3. Select элементы
332
+
333
+ ```typescript
334
+ interface SelectElement extends PageElement {
335
+ type: 'select',
336
+
337
+ // Базовые свойства
338
+ multiple: boolean, // Множественный выбор
339
+ required: boolean, // Обязательное поле
340
+ disabled: boolean, // Элемент заблокирован
341
+ readonly: boolean, // Только для чтения (если поддерживается)
342
+ size?: number, // Количество видимых опций (для multiple)
343
+
344
+ // Опции
345
+ options: Array<{
346
+ value: string, // Значение опции (атрибут value)
347
+ text: string, // Отображаемый текст
348
+ index: number, // Индекс опции (0-based)
349
+ selected: boolean, // Выбрана ли опция
350
+ disabled: boolean, // Заблокирована ли опция
351
+ group?: string, // Название группы (optgroup label), если есть
352
+ groupIndex?: number // Индекс в группе (для optgroup)
353
+ }>,
354
+
355
+ // Текущий выбор
356
+ selectedValues: string[], // Массив выбранных значений (value)
357
+ selectedTexts: string[], // Массив выбранных текстов (для отображения)
358
+ selectedIndices: number[], // Массив индексов выбранных опций
359
+ selectedValue: string | null, // Первое выбранное значение (для удобства)
360
+ selectedText: string | null, // Первый выбранный текст (для удобства)
361
+ selectedIndex: number, // Первый выбранный индекс (-1 если ничего не выбрано)
362
+
363
+ // Группировка (optgroup)
364
+ hasGroups: boolean, // Есть ли группы (optgroup)
365
+ groups?: Array<{
366
+ label: string, // Название группы
367
+ disabled: boolean, // Группа заблокирована
368
+ optionIndices: number[] // Индексы опций в этой группе
369
+ }>,
370
+
371
+ // UI Framework информация (v2.6.0+)
372
+ uiFramework?: {
373
+ name: string, // 'mui' | 'antd' | 'chakra' | 'bootstrap' | 'vuetify' | 'semantic' | null
374
+ version?: string, // Версия библиотеки (если определена)
375
+ component?: string, // Название компонента ('Select', 'Dropdown', etc.)
376
+ customDropdown: boolean, // true если кастомный dropdown (не нативный <select>)
377
+ expanded?: boolean, // Развернут ли dropdown (если кастомный)
378
+ searchable?: boolean // Поддерживает ли поиск (если кастомный)
379
+ },
380
+
381
+ // Валидация
382
+ validation: {
383
+ required: boolean,
384
+ customValidity?: string // Кастомное сообщение валидации
385
+ },
386
+
387
+ // Метаданные
388
+ name?: string, // Атрибут name
389
+ form?: string, // ID формы (атрибут form)
390
+ autocomplete?: string, // Атрибут autocomplete
391
+
392
+ // Доступные действия
393
+ actions: [
394
+ { type: 'selectOption', params: { value?: string, text?: string, index?: number } },
395
+ { type: 'selectMultiple', params: { values: string[] } }, // для multiple
396
+ { type: 'deselectOption', params: { value?: string, text?: string, index?: number } }, // для multiple
397
+ { type: 'deselectAll' }, // для multiple
398
+ { type: 'focus' },
399
+ { type: 'blur' },
400
+ { type: 'setStyles', params: { styles: StylePair[] } },
401
+ { type: 'scrollTo' }
402
+ ]
403
+ }
404
+ ```
405
+
406
+ **Особенности Select элементов:**
407
+
408
+ 1. **Нативные vs Кастомные:**
409
+ - Нативные `<select>` элементы всегда возвращают полную информацию об опциях
410
+ - Кастомные dropdown (MUI, Ant Design, etc.) могут иметь `uiFramework.customDropdown: true`
411
+ - Для кастомных dropdown опции могут быть недоступны, если dropdown не раскрыт
412
+
413
+ 2. **Группировка опций (optgroup):**
414
+ - Если используется `<optgroup>`, каждая опция получает поле `group` с названием группы
415
+ - Массив `groups` содержит информацию о всех группах
416
+ - `groupIndex` указывает позицию опции внутри группы
417
+
418
+ 3. **Множественный выбор (multiple):**
419
+ - `selectedValues`, `selectedTexts`, `selectedIndices` содержат все выбранные элементы
420
+ - Для удобства `selectedValue`, `selectedText`, `selectedIndex` содержат первый выбранный элемент
421
+
422
+ 4. **UI Framework Detection (v2.6.0+):**
423
+ - Автоматически определяется используемая UI-библиотека
424
+ - Для MUI Select, Ant Design Select, Chakra Select, etc. заполняется `uiFramework`
425
+ - `customDropdown: true` означает, что элемент не является нативным `<select>`
426
+
427
+ **Примеры:**
428
+
429
+ ```typescript
430
+ // Нативный <select>
431
+ {
432
+ type: 'select',
433
+ multiple: false,
434
+ options: [
435
+ { value: 'US', text: 'United States', index: 0, selected: true, disabled: false },
436
+ { value: 'UK', text: 'United Kingdom', index: 1, selected: false, disabled: false }
437
+ ],
438
+ selectedValue: 'US',
439
+ selectedText: 'United States',
440
+ selectedIndex: 0,
441
+ hasGroups: false,
442
+ uiFramework: null
443
+ }
444
+
445
+ // Select с optgroup
446
+ {
447
+ type: 'select',
448
+ options: [
449
+ { value: 'us', text: 'United States', index: 0, selected: false, group: 'North America', groupIndex: 0 },
450
+ { value: 'ca', text: 'Canada', index: 1, selected: false, group: 'North America', groupIndex: 1 },
451
+ { value: 'uk', text: 'United Kingdom', index: 2, selected: true, group: 'Europe', groupIndex: 0 }
452
+ ],
453
+ hasGroups: true,
454
+ groups: [
455
+ { label: 'North America', disabled: false, optionIndices: [0, 1] },
456
+ { label: 'Europe', disabled: false, optionIndices: [2] }
457
+ ],
458
+ selectedValue: 'uk'
459
+ }
460
+
461
+ // MUI Select (кастомный)
462
+ {
463
+ type: 'select',
464
+ options: [
465
+ { value: '1', text: 'Option 1', index: 0, selected: true }
466
+ ],
467
+ uiFramework: {
468
+ name: 'mui',
469
+ version: '5.x',
470
+ component: 'Select',
471
+ customDropdown: true,
472
+ expanded: false,
473
+ searchable: false
474
+ },
475
+ selectedValue: '1'
476
+ }
477
+ ```
478
+
479
+ #### 2.4. Button элементы
480
+
481
+ ```typescript
482
+ interface ButtonElement extends PageElement {
483
+ type: 'button',
484
+
485
+ buttonType: 'button' | 'submit' | 'reset',
486
+ disabled: boolean,
487
+
488
+ // Контекст
489
+ formId?: string, // ID формы если кнопка внутри формы
490
+ role?: string, // ARIA role
491
+ ariaLabel?: string,
492
+
493
+ actions: [
494
+ { type: 'click', params: { waitForNavigation?: boolean } },
495
+ { type: 'hover' },
496
+ { type: 'focus' },
497
+ { type: 'setStyles', params: { styles: StylePair[] } },
498
+ { type: 'scrollTo' }
499
+ ]
500
+ }
501
+ ```
502
+
503
+ #### 2.5. Link элементы
504
+
505
+ ```typescript
506
+ interface LinkElement extends PageElement {
507
+ type: 'link',
508
+
509
+ href: string,
510
+ target?: '_blank' | '_self' | '_parent' | '_top',
511
+ download?: string,
512
+ rel?: string,
513
+
514
+ actions: [
515
+ { type: 'click', params: { waitForNavigation?: boolean } },
516
+ { type: 'hover' },
517
+ { type: 'setStyles', params: { styles: StylePair[] } },
518
+ { type: 'scrollTo' }
519
+ ]
520
+ }
521
+ ```
522
+
523
+ #### 2.6. Form элементы (композитная модель)
524
+
525
+ ```typescript
526
+ interface FormElement extends PageElement {
527
+ type: 'form',
528
+
529
+ method: 'GET' | 'POST',
530
+ action?: string,
531
+ enctype?: string,
532
+
533
+ // Поля формы (группированные)
534
+ fields: {
535
+ inputs: Record<string, InputElement>,
536
+ textareas: Record<string, TextareaElement>,
537
+ selects: Record<string, SelectElement>,
538
+ checkboxes: Record<string, CheckboxElement>,
539
+ radios: Record<string, RadioElement>
540
+ },
541
+
542
+ // Кнопки формы
543
+ submitButtons: ButtonElement[],
544
+ resetButtons: ButtonElement[],
545
+
546
+ // Валидация всей формы
547
+ validation: {
548
+ valid: boolean, // Валидна ли форма в целом
549
+ requiredFields: string[], // ID обязательных полей
550
+ invalidFields: string[] // ID невалидных полей
551
+ },
552
+
553
+ actions: [
554
+ {
555
+ type: 'fillForm',
556
+ params: {
557
+ fields: Record<fieldId, string>, // Заполнить несколько полей
558
+ submit?: boolean // Автоматически отправить
559
+ }
560
+ },
561
+ { type: 'submit', params: { waitForNavigation?: boolean } },
562
+ { type: 'reset' },
563
+ { type: 'validateForm' }, // Запустить HTML5 валидацию
564
+ { type: 'setStyles', params: { styles: StylePair[] } },
565
+ { type: 'scrollTo' }
566
+ ]
567
+ }
568
+ ```
569
+
570
+ #### 2.7. Checkbox/Radio элементы
571
+
572
+ ```typescript
573
+ interface CheckboxElement extends PageElement {
574
+ type: 'checkbox',
575
+
576
+ checked: boolean,
577
+ required: boolean,
578
+ disabled: boolean,
579
+ value: string,
580
+
581
+ // Для связанных радио
582
+ name?: string, // Группа радио-кнопок
583
+
584
+ actions: [
585
+ { type: 'toggle' }, // Переключить
586
+ { type: 'check' }, // Установить checked
587
+ { type: 'uncheck' }, // Снять checked
588
+ { type: 'click' },
589
+ { type: 'setStyles', params: { styles: StylePair[] } },
590
+ { type: 'scrollTo' }
591
+ ]
592
+ }
593
+
594
+ interface RadioElement extends PageElement {
595
+ type: 'radio',
596
+
597
+ checked: boolean,
598
+ required: boolean,
599
+ disabled: boolean,
600
+ value: string,
601
+ name: string, // Группа радио
602
+
603
+ // Другие опции в группе
604
+ groupOptions: Array<{
605
+ elementId: string,
606
+ value: string,
607
+ text?: string,
608
+ checked: boolean
609
+ }>,
610
+
611
+ actions: [
612
+ { type: 'select' }, // Выбрать эту опцию
613
+ { type: 'click' },
614
+ { type: 'setStyles', params: { styles: StylePair[] } },
615
+ { type: 'scrollTo' }
616
+ ]
617
+ }
618
+ ```
619
+
620
+ #### 2.8. Generic/Non-interactive элементы
621
+
622
+ ```typescript
623
+ interface GenericElement extends PageElement {
624
+ type: 'generic',
625
+
626
+ role?: string, // ARIA role
627
+ ariaLabel?: string,
628
+
629
+ // Интерактивность
630
+ clickable: boolean, // Имеет onclick или cursor:pointer
631
+ hoverable: boolean, // Имеет hover эффекты
632
+
633
+ actions: [
634
+ { type: 'click' }?, // Только если clickable
635
+ { type: 'hover' }?, // Только если hoverable
636
+ { type: 'setStyles', params: { styles: StylePair[] } },
637
+ { type: 'scrollTo' },
638
+ { type: 'getComputedCss', params: { category?: string } },
639
+ { type: 'getBoxModel' }
640
+ ]
641
+ }
642
+ ```
643
+
644
+ #### 2.9. Section элементы (группировка)
645
+
646
+ ```typescript
647
+ interface SectionElement extends PageElement {
648
+ type: 'section',
649
+
650
+ sectionType: 'header' | 'nav' | 'main' | 'article' | 'aside' | 'footer' | 'form' | 'div',
651
+ role?: string,
652
+
653
+ // Дочерние элементы
654
+ childIds: string[],
655
+
656
+ // Семантика
657
+ label?: string, // aria-label или heading внутри
658
+ landmark?: string, // ARIA landmark role
659
+
660
+ actions: [
661
+ { type: 'setStyles', params: { styles: StylePair[] } },
662
+ { type: 'scrollTo' }
663
+ ]
664
+ }
665
+ ```
666
+
667
+ ---
668
+
669
+ ### 3. Генерация ID элементов
670
+
671
+ #### Стратегия присвоения ID:
672
+
673
+ ```typescript
674
+ function generateElementId(element: Element, index: number): string {
675
+ // Приоритет:
676
+ // 1. data-testid
677
+ if (element.dataset.testid) {
678
+ return `testid:${element.dataset.testid}`;
679
+ }
680
+
681
+ // 2. id атрибут
682
+ if (element.id) {
683
+ return `id:${element.id}`;
684
+ }
685
+
686
+ // 3. Семантический путь + тип + индекс
687
+ const path = getSemanticPath(element);
688
+ const type = getElementType(element);
689
+ return `${type}:${path}:${index}`;
690
+ }
691
+
692
+ // Примеры ID:
693
+ // - testid:login-button
694
+ // - id:email-input
695
+ // - input:form[name="login"]:0
696
+ // - button:header>nav:2
697
+ // - link:footer:5
698
+ ```
699
+
700
+ #### Валидация ID
701
+
702
+ ```typescript
703
+ interface ElementIdValidator {
704
+ pageId: string, // Берется из page.url() + timestamp
705
+ elementIds: Set<string>, // Валидные ID в рамках модели
706
+
707
+ validate(elementId: string): boolean {
708
+ return this.elementIds.has(elementId);
709
+ }
710
+ }
711
+ ```
712
+
713
+ При выполнении действия проверяется:
714
+ 1. `pageId` совпадает (страница не перезагружалась)
715
+ 2. `elementId` существует в модели
716
+
717
+ Если валидация не прошла - возвращается ошибка с предложением вызвать `updatePageObject` или `getPageObject`.
718
+
719
+ ---
720
+
721
+ ### 4. Группировка элементов
722
+
723
+ #### По типу (default: `groupBy: 'type'`)
724
+
725
+ ```typescript
726
+ {
727
+ groups: {
728
+ inputs: {
729
+ count: 5,
730
+ elementIds: ['input:form[0]:0', 'input:form[0]:1', ...]
731
+ },
732
+ buttons: { count: 3, elementIds: [...] },
733
+ links: { count: 10, elementIds: [...] },
734
+ forms: [
735
+ {
736
+ formId: 'form:0',
737
+ fields: { inputs: [...], selects: [...] },
738
+ submitButtons: [...]
739
+ }
740
+ ]
741
+ }
742
+ }
743
+ ```
744
+
745
+ #### По секциям (`groupBy: 'section'`)
746
+
747
+ ```typescript
748
+ {
749
+ groups: {
750
+ sections: [
751
+ {
752
+ sectionId: 'section:header',
753
+ label: 'Site Header',
754
+ childIds: ['link:header:0', 'button:header:0', ...]
755
+ },
756
+ {
757
+ sectionId: 'section:main>form',
758
+ label: 'Login Form',
759
+ childIds: ['input:form[0]:0', 'input:form[0]:1', 'button:form[0]:0']
760
+ }
761
+ ]
762
+ }
763
+ }
764
+ ```
765
+
766
+ #### Плоская (`groupBy: 'flat'`)
767
+
768
+ Просто карта `elements: { [id]: element }` без группировки.
769
+
770
+ ---
771
+
772
+ ## API инструментов
773
+
774
+ ### Полная спецификация инструментов
775
+
776
+ #### 1. `getPageObject`
777
+
778
+ **Описание:** Получить объектную модель текущей страницы
779
+
780
+ **Параметры:**
781
+ ```typescript
782
+ {
783
+ refresh?: boolean, // Пересчитать модель (default: false)
784
+ includeNonInteractive?: boolean, // Включить статичные элементы (default: false)
785
+ includeStyles?: boolean, // Включить computed styles для каждого элемента (default: false)
786
+ groupBy?: 'type' | 'section' | 'flat', // Группировка элементов (default: 'type')
787
+ maxElements?: number // Лимит элементов (default: 200)
788
+ }
789
+ ```
790
+
791
+ **Возвращает:** `PageObjectModel` (см. раздел 2)
792
+
793
+ **Кэширование:** результат кэшируется по URL до `refresh: true` или навигации
794
+
795
+ **Примеры использования:**
796
+ ```javascript
797
+ // Базовое использование
798
+ const page = await getPageObject();
799
+ console.log(page.groups.forms); // Все формы
800
+
801
+ // С дополнительными элементами
802
+ const page = await getPageObject({
803
+ includeNonInteractive: true,
804
+ groupBy: 'section'
805
+ });
806
+ console.log(page.groups.sections); // Элементы по секциям страницы
807
+ ```
808
+
809
+ ---
810
+
811
+ #### 2. `performAction`
812
+
813
+ **Описание:** Выполнить действие над элементом по его ID
814
+
815
+ **Параметры:**
816
+ ```typescript
817
+ {
818
+ pageId: string, // ID страницы из getPageObject
819
+ elementId: string, // ID элемента из getPageObject
820
+ action: ActionType, // Тип действия
821
+ params?: object, // Параметры действия (зависят от типа)
822
+ screenshot?: boolean, // Сделать скриншот после (default: false)
823
+ waitAfter?: number // Ждать N мс после действия (default: 500)
824
+ }
825
+ ```
826
+
827
+ **Поддерживаемые действия:**
828
+
829
+ | Action Type | Параметры | Применимо к |
830
+ |------------|-----------|-------------|
831
+ | `click` | `{ waitForNavigation?: boolean }` | button, link, generic (clickable) |
832
+ | `type` | `{ text: string, delay?: number, clearFirst?: boolean }` | input, textarea |
833
+ | `clear` | - | input, textarea |
834
+ | `focus` | - | input, textarea, select, button |
835
+ | `blur` | - | input, textarea |
836
+ | `hover` | - | любой видимый элемент |
837
+ | `scrollTo` | `{ behavior?: 'auto' \| 'smooth' }` | любой элемент |
838
+ | `selectOption` | `{ value?: string, text?: string, index?: number }` | select |
839
+ | `toggle` | - | checkbox |
840
+ | `check` | - | checkbox |
841
+ | `uncheck` | - | checkbox |
842
+ | `select` | - | radio |
843
+ | `submit` | `{ waitForNavigation?: boolean }` | form |
844
+ | `reset` | - | form |
845
+ | `fillForm` | `{ fields: Record<fieldId, value>, submit?: boolean }` | form |
846
+ | `validateForm` | - | form |
847
+ | `setStyles` | `{ styles: Array<{name, value}> }` | любой элемент |
848
+ | `getComputedCss` | `{ category?: 'layout'\|'typography'\|... }` | любой элемент |
849
+ | `getBoxModel` | - | любой элемент |
850
+
851
+ **Возвращает:**
852
+ ```typescript
853
+ {
854
+ success: boolean,
855
+ message?: string, // Сообщение об ошибке или успехе
856
+ result?: any, // Результат действия (напр. CSS для getComputedCss)
857
+ screenshot?: string, // Base64 PNG если screenshot: true
858
+ pageUpdated?: boolean // true если страница изменилась (навигация, reload)
859
+ }
860
+ ```
861
+
862
+ **Примеры:**
863
+ ```javascript
864
+ // Заполнить input
865
+ await performAction({
866
+ pageId: page.pageId,
867
+ elementId: 'input:form[0]:0',
868
+ action: 'type',
869
+ params: { text: 'user@example.com' }
870
+ });
871
+
872
+ // Кликнуть кнопку
873
+ await performAction({
874
+ pageId: page.pageId,
875
+ elementId: 'button:form[0]:0',
876
+ action: 'click',
877
+ params: { waitForNavigation: true },
878
+ screenshot: true
879
+ });
880
+
881
+ // Заполнить всю форму
882
+ await performAction({
883
+ pageId: page.pageId,
884
+ elementId: 'form:0',
885
+ action: 'fillForm',
886
+ params: {
887
+ fields: {
888
+ 'input:form[0]:0': 'user@example.com',
889
+ 'input:form[0]:1': 'password123',
890
+ 'select:form[0]:0': 'option1'
891
+ },
892
+ submit: true
893
+ }
894
+ });
895
+ ```
896
+
897
+ ---
898
+
899
+ #### 3. `updatePageObject`
900
+
901
+ **Описание:** Обновить модель страницы (после динамических изменений)
902
+
903
+ **Параметры:**
904
+ ```typescript
905
+ {
906
+ pageId: string, // ID страницы
907
+ elementIds?: string[], // Обновить только эти элементы (или все)
908
+ includeNew?: boolean, // Добавить новые элементы (default: true)
909
+ removeDeleted?: boolean // Удалить несуществующие элементы (default: true)
910
+ }
911
+ ```
912
+
913
+ **Возвращает:** обновленный `PageObjectModel`
914
+
915
+ **Использование:** после AJAX запросов, React re-renders, динамических изменений DOM
916
+
917
+ **Пример:**
918
+ ```javascript
919
+ // После клика, который загрузил новые элементы
920
+ await performAction({ ... });
921
+
922
+ // Обновить модель
923
+ const updatedPage = await updatePageObject({
924
+ pageId: page.pageId,
925
+ includeNew: true
926
+ });
927
+
928
+ console.log(updatedPage.metadata.totalElements); // Новое количество
929
+ ```
930
+
931
+ ---
932
+
933
+ #### 4. `queryElements`
934
+
935
+ **Описание:** Найти элементы в модели по критериям
936
+
937
+ **Параметры:**
938
+ ```typescript
939
+ {
940
+ pageId: string,
941
+ query: {
942
+ type?: ElementType | ElementType[], // Фильтр по типу
943
+ text?: string, // Поиск по тексту (substring)
944
+ attributes?: Record<string, string>, // Фильтр по атрибутам
945
+ visible?: boolean, // Только видимые/невидимые
946
+ enabled?: boolean, // Только enabled
947
+ inForm?: boolean, // Только внутри форм
948
+ parentId?: string, // Дочерние элементы
949
+ hasAction?: ActionType // Элементы с определенным действием
950
+ },
951
+ limit?: number, // default: 20
952
+ offset?: number // Пагинация (default: 0)
953
+ }
954
+ ```
955
+
956
+ **Возвращает:**
957
+ ```typescript
958
+ {
959
+ elementIds: string[],
960
+ total: number, // Всего найдено
961
+ hasMore: boolean // Есть еще результаты
962
+ }
963
+ ```
964
+
965
+ **Примеры:**
966
+ ```javascript
967
+ // Найти все кнопки submit
968
+ const { elementIds } = await queryElements({
969
+ pageId: page.pageId,
970
+ query: {
971
+ type: 'button',
972
+ attributes: { type: 'submit' }
973
+ }
974
+ });
975
+
976
+ // Найти input с текстом "email"
977
+ const { elementIds } = await queryElements({
978
+ pageId: page.pageId,
979
+ query: {
980
+ type: 'input',
981
+ text: 'email'
982
+ }
983
+ });
984
+
985
+ // Найти все элементы в форме
986
+ const { elementIds } = await queryElements({
987
+ pageId: page.pageId,
988
+ query: {
989
+ parentId: 'form:0'
990
+ }
991
+ });
992
+ ```
993
+
994
+ ---
995
+
996
+ #### 5. `getElementDetails`
997
+
998
+ **Описание:** Получить детальную информацию об элементе
999
+
1000
+ **Параметры:**
1001
+ ```typescript
1002
+ {
1003
+ pageId: string,
1004
+ elementId: string,
1005
+ includeStyles?: boolean, // Включить computed styles (default: false)
1006
+ includeBoxModel?: boolean, // Включить box model (default: false)
1007
+ includeChildren?: boolean // Включить детали дочерних элементов (default: false)
1008
+ }
1009
+ ```
1010
+
1011
+ **Возвращает:** полный объект `PageElement` с запрошенными деталями
1012
+
1013
+ **Использование:** когда нужна детальная информация об одном элементе (избегаем передачи всей модели)
1014
+
1015
+ ---
1016
+
1017
+ ## План разработки
1018
+
1019
+ ### Фаза 1: Базовая инфраструктура (Week 1)
1020
+
1021
+ #### 1.1. Модуль генерации ID элементов
1022
+ **Файл:** `pom/element-id-generator.js`
1023
+
1024
+ **Задачи:**
1025
+ - [ ] Функция `generateElementId(element, index)` с приоритетом testid > id > semantic path
1026
+ - [ ] Функция `getSemanticPath(element)` для построения пути
1027
+ - [ ] Функция `getElementType(element)` для определения типа
1028
+ - [ ] Unit тесты для генерации ID
1029
+
1030
+ **Зависимости:** нет
1031
+
1032
+ ---
1033
+
1034
+ #### 1.2. Модуль валидации ID
1035
+ **Файл:** `pom/element-id-validator.js`
1036
+
1037
+ **Задачи:**
1038
+ - [ ] Класс `ElementIdValidator` с методами validate/add/remove
1039
+ - [ ] Генерация `pageId` на основе URL + timestamp
1040
+ - [ ] Обработка невалидных ID с рекомендациями
1041
+ - [ ] Unit тесты
1042
+
1043
+ **Зависимости:** 1.1
1044
+
1045
+ ---
1046
+
1047
+ #### 1.3. Модуль создания моделей элементов
1048
+ **Файл:** `pom/element-model-factory.js`
1049
+
1050
+ **Задачи:**
1051
+ - [ ] Функции создания моделей для каждого типа элемента:
1052
+ - `createInputElement(element, id, selector)`
1053
+ - `createButtonElement(...)`
1054
+ - `createSelectElement(...)`
1055
+ - `createFormElement(...)`
1056
+ - и т.д. (10+ типов)
1057
+ - [ ] Извлечение метаданных (bounds, attributes, validation)
1058
+ - [ ] Определение доступных actions для каждого типа
1059
+ - [ ] Unit тесты
1060
+
1061
+ **Зависимости:** 1.1
1062
+
1063
+ ---
1064
+
1065
+ ### Фаза 2: Инструмент getPageObject (Week 2)
1066
+
1067
+ #### 2.1. Сбор элементов со страницы
1068
+ **Файл:** `pom/page-scanner.js`
1069
+
1070
+ **Задачи:**
1071
+ - [ ] Функция `scanPage(page, options)` - обход DOM через page.evaluate()
1072
+ - [ ] Сбор интерактивных элементов (input, button, select, a, textarea, form)
1073
+ - [ ] Опциональный сбор неинтерактивных элементов
1074
+ - [ ] Извлечение атрибутов, bounds, visibility для каждого элемента
1075
+ - [ ] Построение иерархии (parent/child relationships)
1076
+ - [ ] Интеграционные тесты
1077
+
1078
+ **Зависимости:** 1.1, 1.3
1079
+
1080
+ ---
1081
+
1082
+ #### 2.2. Группировка элементов
1083
+ **Файл:** `pom/element-grouper.js`
1084
+
1085
+ **Задачи:**
1086
+ - [ ] Группировка по типу (`groupBy: 'type'`)
1087
+ - [ ] Группировка по секциям (`groupBy: 'section'`) - поиск header/nav/main/footer
1088
+ - [ ] Плоская структура (`groupBy: 'flat'`)
1089
+ - [ ] Специальная обработка форм (fields + buttons)
1090
+ - [ ] Unit тесты
1091
+
1092
+ **Зависимости:** 1.3
1093
+
1094
+ ---
1095
+
1096
+ #### 2.3. Инструмент getPageObject
1097
+ **Файл:** `pom/tools/get-page-object.js`
1098
+
1099
+ **Задачи:**
1100
+ - [ ] Реализация MCP tool handler
1101
+ - [ ] Интеграция с кэшированием (pageAnalysisCache)
1102
+ - [ ] Поддержка параметров (refresh, includeNonInteractive, groupBy, maxElements)
1103
+ - [ ] Обработка ошибок
1104
+ - [ ] Документация
1105
+ - [ ] Интеграционные тесты
1106
+
1107
+ **Zod схема:**
1108
+ ```javascript
1109
+ getPageObject: z.object({
1110
+ refresh: z.boolean().optional(),
1111
+ includeNonInteractive: z.boolean().optional(),
1112
+ includeStyles: z.boolean().optional(),
1113
+ groupBy: z.enum(['type', 'section', 'flat']).optional(),
1114
+ maxElements: z.number().optional()
1115
+ })
1116
+ ```
1117
+
1118
+ **Зависимости:** 2.1, 2.2, 1.2
1119
+
1120
+ ---
1121
+
1122
+ ### Фаза 3: Инструмент performAction (Week 3)
1123
+
1124
+ #### 3.1. Модуль выполнения действий
1125
+ **Файл:** `pom/action-executor.js`
1126
+
1127
+ **Задачи:**
1128
+ - [ ] Функция `executeAction(page, element, action, params)` - роутинг по типу действия
1129
+ - [ ] Реализация всех типов действий:
1130
+ - **click** - через element.click()
1131
+ - **type** - через element.type() с clearFirst
1132
+ - **clear** - через triple-click + backspace
1133
+ - **focus/blur** - через element.focus()
1134
+ - **hover** - через element.hover()
1135
+ - **scrollTo** - через element.scrollIntoView()
1136
+ - **selectOption** - через page.select()
1137
+ - **toggle/check/uncheck** - для checkbox
1138
+ - **select** - для radio (найти по name и кликнуть)
1139
+ - **submit** - через form.submit() или кнопка submit
1140
+ - **reset** - через form.reset()
1141
+ - **fillForm** - итерация по полям + submit
1142
+ - **validateForm** - вызов reportValidity()
1143
+ - **setStyles** - через page.evaluate()
1144
+ - **getComputedCss** - через CDP
1145
+ - **getBoxModel** - через CDP
1146
+ - [ ] Поддержка waitForNavigation для click/submit
1147
+ - [ ] Поддержка screenshot после действия
1148
+ - [ ] Обработка ошибок (элемент не найден, не видим, disabled)
1149
+ - [ ] Unit + интеграционные тесты
1150
+
1151
+ **Зависимости:** нет (использует Puppeteer API)
1152
+
1153
+ ---
1154
+
1155
+ #### 3.2. Инструмент performAction
1156
+ **Файл:** `pom/tools/perform-action.js`
1157
+
1158
+ **Задачи:**
1159
+ - [ ] Реализация MCP tool handler
1160
+ - [ ] Валидация pageId и elementId через ElementIdValidator
1161
+ - [ ] Получение селектора из модели по elementId
1162
+ - [ ] Вызов action-executor
1163
+ - [ ] Обработка результата (success, message, result, screenshot)
1164
+ - [ ] Определение изменения страницы (pageUpdated)
1165
+ - [ ] Документация
1166
+ - [ ] Интеграционные тесты
1167
+
1168
+ **Zod схема:**
1169
+ ```javascript
1170
+ performAction: z.object({
1171
+ pageId: z.string(),
1172
+ elementId: z.string(),
1173
+ action: z.enum(['click', 'type', 'clear', 'focus', 'blur', 'hover', 'scrollTo',
1174
+ 'selectOption', 'toggle', 'check', 'uncheck', 'select',
1175
+ 'submit', 'reset', 'fillForm', 'validateForm',
1176
+ 'setStyles', 'getComputedCss', 'getBoxModel']),
1177
+ params: z.record(z.any()).optional(),
1178
+ screenshot: z.boolean().optional(),
1179
+ waitAfter: z.number().optional()
1180
+ })
1181
+ ```
1182
+
1183
+ **Зависимости:** 3.1, 1.2, 2.3 (для получения модели)
1184
+
1185
+ ---
1186
+
1187
+ ### Фаза 4: Дополнительные инструменты (Week 4)
1188
+
1189
+ #### 4.1. Инструмент updatePageObject
1190
+ **Файл:** `pom/tools/update-page-object.js`
1191
+
1192
+ **Задачи:**
1193
+ - [ ] Реализация MCP tool handler
1194
+ - [ ] Валидация pageId
1195
+ - [ ] Обновление конкретных элементов (по elementIds) или всех
1196
+ - [ ] Добавление новых элементов (includeNew)
1197
+ - [ ] Удаление несуществующих (removeDeleted)
1198
+ - [ ] Обновление кэша
1199
+ - [ ] Возврат обновленной модели
1200
+ - [ ] Документация
1201
+ - [ ] Интеграционные тесты
1202
+
1203
+ **Zod схема:**
1204
+ ```javascript
1205
+ updatePageObject: z.object({
1206
+ pageId: z.string(),
1207
+ elementIds: z.array(z.string()).optional(),
1208
+ includeNew: z.boolean().optional(),
1209
+ removeDeleted: z.boolean().optional()
1210
+ })
1211
+ ```
1212
+
1213
+ **Зависимости:** 2.1, 2.2, 1.2
1214
+
1215
+ ---
1216
+
1217
+ #### 4.2. Инструмент queryElements
1218
+ **Файл:** `pom/tools/query-elements.js`
1219
+
1220
+ **Задачи:**
1221
+ - [ ] Реализация MCP tool handler
1222
+ - [ ] Валидация pageId
1223
+ - [ ] Получение модели из кэша
1224
+ - [ ] Фильтрация элементов по критериям:
1225
+ - type (поддержка массива типов)
1226
+ - text (substring, case-insensitive)
1227
+ - attributes (partial match)
1228
+ - visible/enabled
1229
+ - inForm (проверка parentId)
1230
+ - parentId (дочерние элементы)
1231
+ - hasAction (проверка actions)
1232
+ - [ ] Пагинация (limit/offset)
1233
+ - [ ] Возврат elementIds + metadata
1234
+ - [ ] Документация
1235
+ - [ ] Unit + интеграционные тесты
1236
+
1237
+ **Zod схема:**
1238
+ ```javascript
1239
+ queryElements: z.object({
1240
+ pageId: z.string(),
1241
+ query: z.object({
1242
+ type: z.union([z.string(), z.array(z.string())]).optional(),
1243
+ text: z.string().optional(),
1244
+ attributes: z.record(z.string()).optional(),
1245
+ visible: z.boolean().optional(),
1246
+ enabled: z.boolean().optional(),
1247
+ inForm: z.boolean().optional(),
1248
+ parentId: z.string().optional(),
1249
+ hasAction: z.string().optional()
1250
+ }),
1251
+ limit: z.number().optional(),
1252
+ offset: z.number().optional()
1253
+ })
1254
+ ```
1255
+
1256
+ **Зависимости:** 2.3, 1.2
1257
+
1258
+ ---
1259
+
1260
+ #### 4.3. Инструмент getElementDetails
1261
+ **Файл:** `pom/tools/get-element-details.js`
1262
+
1263
+ **Задачи:**
1264
+ - [ ] Реализация MCP tool handler
1265
+ - [ ] Валидация pageId и elementId
1266
+ - [ ] Получение базовой модели элемента
1267
+ - [ ] Опциональное добавление computed styles (через CDP)
1268
+ - [ ] Опциональное добавление box model (через CDP)
1269
+ - [ ] Опциональное добавление деталей дочерних элементов
1270
+ - [ ] Документация
1271
+ - [ ] Интеграционные тесты
1272
+
1273
+ **Zod схема:**
1274
+ ```javascript
1275
+ getElementDetails: z.object({
1276
+ pageId: z.string(),
1277
+ elementId: z.string(),
1278
+ includeStyles: z.boolean().optional(),
1279
+ includeBoxModel: z.boolean().optional(),
1280
+ includeChildren: z.boolean().optional()
1281
+ })
1282
+ ```
1283
+
1284
+ **Зависимости:** 2.3, 1.2
1285
+
1286
+ ---
1287
+
1288
+ ### Фаза 5: Интеграция и документация (Week 5)
1289
+
1290
+ #### 5.1. Интеграция в основной MCP server
1291
+ **Файл:** `index.js`, `tools/tool-schemas.js`, `server/tool-definitions.js`
1292
+
1293
+ **Задачи:**
1294
+ - [ ] Добавить импорты всех POM инструментов
1295
+ - [ ] Зарегистрировать инструменты в MCP server
1296
+ - [ ] Добавить Zod схемы в tool-schemas.js
1297
+ - [ ] Добавить определения в tool-definitions.js
1298
+ - [ ] Добавить новую группу инструментов 'pom' в tool-groups.js
1299
+ - [ ] Интеграционные тесты для всего MCP сервера
1300
+
1301
+ **Зависимости:** 2.3, 3.2, 4.1, 4.2, 4.3
1302
+
1303
+ ---
1304
+
1305
+ #### 5.2. Документация
1306
+ **Файлы:** `README.md`, `CHANGELOG.md`, `docs/POM_API.md`
1307
+
1308
+ **Задачи:**
1309
+ - [ ] Обновить README.md:
1310
+ - Добавить секцию "Page Object Model API"
1311
+ - Описание концепции
1312
+ - Список инструментов с кратким описанием
1313
+ - Примеры базового использования
1314
+ - Обновить счетчик инструментов (было ~50, стало ~55)
1315
+ - [ ] Обновить CHANGELOG.md:
1316
+ - Новая версия (например, 3.0.0 - major change)
1317
+ - Секция "Added" со списком 5 новых инструментов
1318
+ - Краткое описание концепции POM
1319
+ - [ ] Создать подробную документацию `docs/POM_API.md`:
1320
+ - Полное описание концепции
1321
+ - Спецификация всех инструментов
1322
+ - Примеры использования для распространенных сценариев
1323
+ - Comparison с существующими инструментами (когда использовать POM API vs классические инструменты)
1324
+ - Best practices
1325
+ - [ ] Обновить package.json версию
1326
+
1327
+ **Зависимости:** 5.1
1328
+
1329
+ ---
1330
+
1331
+ #### 5.3. Примеры и тесты
1332
+ **Файлы:** `examples/pom-examples.js`, `tests/pom-integration.test.js`
1333
+
1334
+ **Задачи:**
1335
+ - [ ] Создать файл с примерами использования:
1336
+ - Пример 1: Получение модели и заполнение формы
1337
+ - Пример 2: Поиск элементов и клики
1338
+ - Пример 3: Обновление модели после AJAX
1339
+ - Пример 4: Работа с сложными формами
1340
+ - Пример 5: Изменение стилей элементов
1341
+ - [ ] End-to-end тесты:
1342
+ - Тест на реальной странице с формой
1343
+ - Тест на динамическом сайте (React/Vue)
1344
+ - Тест валидации ID
1345
+ - Тест обработки ошибок
1346
+ - [ ] Performance тесты:
1347
+ - Время генерации модели для больших страниц
1348
+ - Время выполнения действий
1349
+ - Память (размер модели)
1350
+
1351
+ **Зависимости:** 5.1
1352
+
1353
+ ---
1354
+
1355
+ ## Примеры использования
1356
+
1357
+ ### Пример 1: Базовое использование - вход в систему
1358
+
1359
+ ```javascript
1360
+ // Шаг 1: Получить модель страницы
1361
+ const page = await getPageObject();
1362
+
1363
+ console.log(page.groups.forms);
1364
+ // [
1365
+ // {
1366
+ // formId: 'form:0',
1367
+ // fields: {
1368
+ // inputs: {
1369
+ // 'input:form[0]:0': { inputType: 'email', placeholder: 'Email', ... },
1370
+ // 'input:form[0]:1': { inputType: 'password', placeholder: 'Password', ... }
1371
+ // }
1372
+ // },
1373
+ // submitButtons: [
1374
+ // { id: 'button:form[0]:0', text: 'Sign In', ... }
1375
+ // ]
1376
+ // }
1377
+ // ]
1378
+
1379
+ // Шаг 2: Заполнить форму одной командой
1380
+ await performAction({
1381
+ pageId: page.pageId,
1382
+ elementId: 'form:0',
1383
+ action: 'fillForm',
1384
+ params: {
1385
+ fields: {
1386
+ 'input:form[0]:0': 'user@example.com',
1387
+ 'input:form[0]:1': 'password123'
1388
+ },
1389
+ submit: true
1390
+ },
1391
+ screenshot: true
1392
+ });
1393
+
1394
+ // Альтернатива: заполнить поля по отдельности
1395
+ await performAction({
1396
+ pageId: page.pageId,
1397
+ elementId: 'input:form[0]:0',
1398
+ action: 'type',
1399
+ params: { text: 'user@example.com' }
1400
+ });
1401
+
1402
+ await performAction({
1403
+ pageId: page.pageId,
1404
+ elementId: 'input:form[0]:1',
1405
+ action: 'type',
1406
+ params: { text: 'password123' }
1407
+ });
1408
+
1409
+ await performAction({
1410
+ pageId: page.pageId,
1411
+ elementId: 'button:form[0]:0',
1412
+ action: 'click',
1413
+ params: { waitForNavigation: true }
1414
+ });
1415
+ ```
1416
+
1417
+ ---
1418
+
1419
+ ### Пример 2: Поиск элементов и взаимодействие
1420
+
1421
+ ```javascript
1422
+ // Шаг 1: Получить модель
1423
+ const page = await getPageObject({ groupBy: 'section' });
1424
+
1425
+ // Шаг 2: Найти все ссылки в навигации
1426
+ const navSection = page.groups.sections.find(s => s.sectionType === 'nav');
1427
+ const navLinks = navSection.childIds.filter(id =>
1428
+ page.elements[id].type === 'link'
1429
+ );
1430
+
1431
+ console.log(navLinks);
1432
+ // ['link:header>nav:0', 'link:header>nav:1', 'link:header>nav:2']
1433
+
1434
+ // Шаг 3: Найти конкретную ссылку по тексту
1435
+ const { elementIds } = await queryElements({
1436
+ pageId: page.pageId,
1437
+ query: {
1438
+ type: 'link',
1439
+ text: 'Products',
1440
+ parentId: navSection.sectionId
1441
+ }
1442
+ });
1443
+
1444
+ // Шаг 4: Кликнуть на найденную ссылку
1445
+ await performAction({
1446
+ pageId: page.pageId,
1447
+ elementId: elementIds[0],
1448
+ action: 'click',
1449
+ params: { waitForNavigation: true }
1450
+ });
1451
+ ```
1452
+
1453
+ ---
1454
+
1455
+ ### Пример 3: Работа с динамическим контентом
1456
+
1457
+ ```javascript
1458
+ // Шаг 1: Получить начальную модель
1459
+ let page = await getPageObject();
1460
+
1461
+ console.log(page.metadata.totalElements); // 50
1462
+
1463
+ // Шаг 2: Кликнуть на кнопку "Load More"
1464
+ const { elementIds } = await queryElements({
1465
+ pageId: page.pageId,
1466
+ query: {
1467
+ type: 'button',
1468
+ text: 'Load More'
1469
+ }
1470
+ });
1471
+
1472
+ await performAction({
1473
+ pageId: page.pageId,
1474
+ elementId: elementIds[0],
1475
+ action: 'click',
1476
+ waitAfter: 1000
1477
+ });
1478
+
1479
+ // Шаг 3: Обновить модель после загрузки новых элементов
1480
+ page = await updatePageObject({
1481
+ pageId: page.pageId,
1482
+ includeNew: true
1483
+ });
1484
+
1485
+ console.log(page.metadata.totalElements); // 75 (добавилось 25 элементов)
1486
+
1487
+ // Шаг 4: Найти новые элементы
1488
+ const newElements = Object.keys(page.elements).filter(id =>
1489
+ !oldElements.includes(id)
1490
+ );
1491
+ ```
1492
+
1493
+ ---
1494
+
1495
+ ### Пример 4: Изменение стилей элементов
1496
+
1497
+ ```javascript
1498
+ // Шаг 1: Получить модель
1499
+ const page = await getPageObject();
1500
+
1501
+ // Шаг 2: Найти главный заголовок
1502
+ const { elementIds } = await queryElements({
1503
+ pageId: page.pageId,
1504
+ query: {
1505
+ type: 'generic',
1506
+ text: 'Welcome',
1507
+ attributes: { tagName: 'h1' }
1508
+ }
1509
+ });
1510
+
1511
+ // Шаг 3: Изменить стили заголовка
1512
+ await performAction({
1513
+ pageId: page.pageId,
1514
+ elementId: elementIds[0],
1515
+ action: 'setStyles',
1516
+ params: {
1517
+ styles: [
1518
+ { name: 'color', value: 'red' },
1519
+ { name: 'font-size', value: '48px' },
1520
+ { name: 'font-weight', value: 'bold' }
1521
+ ]
1522
+ },
1523
+ screenshot: true
1524
+ });
1525
+ ```
1526
+
1527
+ ---
1528
+
1529
+ ### Пример 5: Валидация формы
1530
+
1531
+ ```javascript
1532
+ // Шаг 1: Получить модель с формой
1533
+ const page = await getPageObject();
1534
+
1535
+ const form = page.groups.forms[0];
1536
+ console.log(form.validation);
1537
+ // {
1538
+ // valid: false,
1539
+ // requiredFields: ['input:form[0]:0', 'input:form[0]:1'],
1540
+ // invalidFields: []
1541
+ // }
1542
+
1543
+ // Шаг 2: Попытаться отправить пустую форму (для показа ошибок)
1544
+ await performAction({
1545
+ pageId: page.pageId,
1546
+ elementId: form.formId,
1547
+ action: 'validateForm'
1548
+ });
1549
+
1550
+ // Шаг 3: Получить детали полей с ошибками
1551
+ for (const fieldId of form.validation.requiredFields) {
1552
+ const details = await getElementDetails({
1553
+ pageId: page.pageId,
1554
+ elementId: fieldId
1555
+ });
1556
+
1557
+ console.log(details.validationState);
1558
+ // { valid: false, message: 'Please fill out this field.' }
1559
+ }
1560
+
1561
+ // Шаг 4: Заполнить поля
1562
+ await performAction({
1563
+ pageId: page.pageId,
1564
+ elementId: form.formId,
1565
+ action: 'fillForm',
1566
+ params: {
1567
+ fields: {
1568
+ 'input:form[0]:0': 'user@example.com',
1569
+ 'input:form[0]:1': 'password123'
1570
+ }
1571
+ }
1572
+ });
1573
+
1574
+ // Шаг 5: Обновить модель и проверить валидацию
1575
+ const updatedPage = await updatePageObject({
1576
+ pageId: page.pageId,
1577
+ elementIds: [form.formId]
1578
+ });
1579
+
1580
+ const updatedForm = updatedPage.elements[form.formId];
1581
+ console.log(updatedForm.validation);
1582
+ // { valid: true, requiredFields: [...], invalidFields: [] }
1583
+
1584
+ // Шаг 6: Отправить форму
1585
+ await performAction({
1586
+ pageId: page.pageId,
1587
+ elementId: form.formId,
1588
+ action: 'submit',
1589
+ params: { waitForNavigation: true }
1590
+ });
1591
+ ```
1592
+
1593
+ ---
1594
+
1595
+ ### Пример 6: Работа с checkbox и radio
1596
+
1597
+ ```javascript
1598
+ // Шаг 1: Получить модель
1599
+ const page = await getPageObject();
1600
+
1601
+ // Шаг 2: Найти все checkbox
1602
+ const { elementIds: checkboxIds } = await queryElements({
1603
+ pageId: page.pageId,
1604
+ query: { type: 'checkbox' }
1605
+ });
1606
+
1607
+ // Шаг 3: Включить все checkbox
1608
+ for (const id of checkboxIds) {
1609
+ await performAction({
1610
+ pageId: page.pageId,
1611
+ elementId: id,
1612
+ action: 'check'
1613
+ });
1614
+ }
1615
+
1616
+ // Шаг 4: Найти radio группу
1617
+ const { elementIds: radioIds } = await queryElements({
1618
+ pageId: page.pageId,
1619
+ query: {
1620
+ type: 'radio',
1621
+ attributes: { name: 'subscription' }
1622
+ }
1623
+ });
1624
+
1625
+ // Шаг 5: Получить детали одного radio (чтобы увидеть все опции группы)
1626
+ const radioDetails = await getElementDetails({
1627
+ pageId: page.pageId,
1628
+ elementId: radioIds[0]
1629
+ });
1630
+
1631
+ console.log(radioDetails.groupOptions);
1632
+ // [
1633
+ // { elementId: 'radio:form[0]:0', value: 'free', text: 'Free', checked: true },
1634
+ // { elementId: 'radio:form[0]:1', value: 'pro', text: 'Pro', checked: false },
1635
+ // { elementId: 'radio:form[0]:2', value: 'enterprise', text: 'Enterprise', checked: false }
1636
+ // ]
1637
+
1638
+ // Шаг 6: Выбрать опцию "Pro"
1639
+ const proOption = radioDetails.groupOptions.find(o => o.value === 'pro');
1640
+ await performAction({
1641
+ pageId: page.pageId,
1642
+ elementId: proOption.elementId,
1643
+ action: 'select'
1644
+ });
1645
+ ```
1646
+
1647
+ ---
1648
+
1649
+ ## Comparison: POM API vs Классические инструменты
1650
+
1651
+ ### Когда использовать POM API:
1652
+
1653
+ ✅ **Сложные формы** - один вызов `fillForm` вместо множества `type` команд
1654
+ ✅ **Многошаговые взаимодействия** - получить модель один раз, использовать ID многократно
1655
+ ✅ **Динамический контент** - `updatePageObject` для обновления модели после AJAX
1656
+ ✅ **Исследование страницы** - `queryElements` для поиска элементов по критериям
1657
+ ✅ **Валидация форм** - детальная информация о required fields и validation state
1658
+ ✅ **Контекстные действия** - знание о том, какие действия доступны для элемента
1659
+
1660
+ ### Когда использовать классические инструменты:
1661
+
1662
+ ✅ **Простые одиночные действия** - быстрый `click(selector)` или `type(selector, text)`
1663
+ ✅ **Известные селекторы** - если уже знаете точный CSS селектор
1664
+ ✅ **Легковесные операции** - не нужна полная модель страницы
1665
+ ✅ **Скриншоты и инспекция** - `screenshot`, `getComputedCss`, `getBoxModel` остаются отдельными инструментами
1666
+
1667
+ ### Гибридный подход:
1668
+
1669
+ Можно комбинировать оба подхода:
1670
+
1671
+ ```javascript
1672
+ // Использовать POM для сложного взаимодействия
1673
+ const page = await getPageObject();
1674
+ await performAction({ elementId: 'form:0', action: 'fillForm', ... });
1675
+
1676
+ // Использовать классические инструменты для быстрого скриншота
1677
+ await screenshot({ selector: 'body' });
1678
+
1679
+ // Использовать POM для поиска элемента
1680
+ const { elementIds } = await queryElements({ query: { text: 'Submit' } });
1681
+
1682
+ // Использовать классический click (если нужен просто клик)
1683
+ await click({ selector: `[data-testid="submit-button"]` });
1684
+ ```
1685
+
1686
+ ---
1687
+
1688
+ ## Расширения и будущие улучшения
1689
+
1690
+ ### Версия 3.1 (будущее):
1691
+
1692
+ 1. **Поддержка Shadow DOM** - работа с Web Components
1693
+ 2. **Поддержка iframe** - вложенные документы
1694
+ 3. **Accessibility tree** - доступ к accessibility информации
1695
+ 4. **Event listeners** - информация о прикрепленных обработчиках событий
1696
+ 5. **Performance metrics** - время рендеринга элементов
1697
+
1698
+ ### Версия 3.2 (будущее):
1699
+
1700
+ 1. **Smart actions** - AI предложения действий на основе контекста
1701
+ 2. **Visual regression** - сравнение скриншотов элементов
1702
+ 3. **Element snapshots** - сохранение и восстановление состояния элементов
1703
+ 4. **Batch operations** - выполнение множественных действий одной командой
1704
+
1705
+ ---
1706
+
1707
+ ## Технические требования
1708
+
1709
+ ### Производительность:
1710
+
1711
+ - Генерация модели для страницы со 100 элементами: < 500ms
1712
+ - Выполнение действия по ID: < 100ms
1713
+ - Обновление модели (частичное): < 200ms
1714
+ - Размер модели в JSON: < 500KB для 100 элементов
1715
+
1716
+ ### Совместимость:
1717
+
1718
+ - Puppeteer 24.x+
1719
+ - Node.js 18+
1720
+ - Chrome/Chromium 120+
1721
+
1722
+ ### Обратная совместимость:
1723
+
1724
+ - Все существующие инструменты продолжают работать без изменений
1725
+ - POM API - это дополнение, а не замена
1726
+
1727
+ ---
1728
+
1729
+ ## Заключение
1730
+
1731
+ Agent Page Object Model (APOM) API - это мощное дополнение к chrometools-mcp, которое позволяет AI агентам работать с браузером как с объектной моделью, а не набором команд.
1732
+
1733
+ **Не путать с:** Существующий инструмент `generatePageObject` остаётся без изменений и продолжает генерировать Test Page Objects для автотестов (Playwright/Selenium).
1734
+
1735
+ **Ключевые преимущества:**
1736
+
1737
+ 1. ⚡ **Меньше запросов** - получить модель один раз, использовать многократно
1738
+ 2. 🎯 **Семантика** - знание о типах элементов и доступных действиях
1739
+ 3. 🔒 **Валидация** - проверка ID элементов и состояния страницы
1740
+ 4. 📊 **Структура** - группировка элементов по типу, секциям, формам
1741
+ 5. 🚀 **Производительность** - кэширование и инкрементальные обновления
1742
+ 6. 💪 **Мощь** - сложные операции (fillForm, validateForm) одной командой
1743
+
1744
+ **Roadmap:**
1745
+
1746
+ - Week 1-2: Базовая инфраструктура + getPageObject
1747
+ - Week 3: performAction
1748
+ - Week 4: updatePageObject, queryElements, getElementDetails
1749
+ - Week 5: Интеграция, документация, тесты
1750
+
1751
+ **Версия:** 3.0.0 (major release)
1752
+
1753
+ ---
1754
+
1755
+ Документация создана: 2026-01-24
1756
+ Версия: 1.0.0