chrometools-mcp 3.3.8 → 3.4.0
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.
- package/CHANGELOG.md +51 -0
- package/README.md +159 -24
- package/SPEC-pom-integration.md +227 -0
- package/SPEC-swagger-api-tools.md +3101 -0
- package/index.js +591 -209
- package/package.json +2 -1
- package/pom/apom-tree-converter.js +5 -26
- package/recorder/page-object-generator.js +45 -1
- package/server/tool-definitions.js +54 -5
- package/server/tool-schemas.js +29 -0
- package/test-swagger-phase1.mjs +959 -0
- package/utils/api-generators/api-models-python.js +448 -0
- package/utils/api-generators/api-models-typescript.js +375 -0
- package/utils/code-generators/code-generator-base.js +111 -6
- package/utils/code-generators/playwright-python.js +74 -0
- package/utils/code-generators/playwright-typescript.js +69 -0
- package/utils/code-generators/pom-integrator.js +373 -0
- package/utils/code-generators/selenium-java.js +72 -0
- package/utils/code-generators/selenium-python.js +75 -0
- package/utils/hints-generator.js +159 -24
- package/utils/openapi/helpers.js +25 -0
- package/utils/openapi/parser.js +448 -0
- package/utils/openapi/ref-resolver.js +149 -0
- package/utils/openapi/type-mapper.js +174 -0
- package/utils/post-click-diagnostics.js +14 -4
- package/nul +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.4.0] - 2026-02-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Adaptive click strategy with elementFromPoint pre-check** — Before each click, verifies the target element is topmost via `document.elementFromPoint()`. If covered by another element (e.g. small button under `<a routerLink>`), uses DOM dispatch to bypass coordinate hit-testing. Fixes clicks on absolutely-positioned elements over links.
|
|
9
|
+
- **Auth redirect detection** — `navigateTo`, `openBrowser`, and `click` now warn when landing on a login page with returnUrl parameter. Broad login detection: password forms, phone/OTP forms, URL path (`/login`, `/signin`, `/auth`), CSS class matching.
|
|
10
|
+
- **Post-click element detachment detection** — Detects when clicked element is removed from DOM during click (Angular `*ngFor` + Zone.js pattern). Shows actionable hint with app fix (trackBy) and executeScript workaround.
|
|
11
|
+
- **Auth redirect in post-click diagnostics** — `formatDiagnosticsForAI` detects navigation to login pages with returnUrl and shows targeted warning.
|
|
12
|
+
|
|
13
|
+
### Performance
|
|
14
|
+
- **findElementsByText early exit** — Stops DOM traversal at 40 results, preventing 120s timeout on heavy Angular Material pages with CDK overlay.
|
|
15
|
+
|
|
16
|
+
## [3.3.9] - 2026-02-08
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **AI Hints: modal content extraction** — Modals now show title, body text (200 chars), and action buttons
|
|
20
|
+
- Expanded selectors: mat-dialog-container, cdk-overlay-pane, `[class*="dialog"]`
|
|
21
|
+
- Topmost modal dedup for pages with multiple modals
|
|
22
|
+
- Actions extracted from `.modal-footer` / `[mat-dialog-actions]` (limit 5)
|
|
23
|
+
|
|
24
|
+
- **AI Hints: dropdown/menu item extraction** — Overlays now list actual option texts
|
|
25
|
+
- 11 overlay selectors: Angular CDK/Material, PrimeNG, Ant Design, custom `select-options`
|
|
26
|
+
- Menu vs dropdown auto-classification (role="menu", role="listbox", menuitem detection)
|
|
27
|
+
- Item text extraction (limit 10 items, shows total count)
|
|
28
|
+
- Deduplication of nested overlay elements
|
|
29
|
+
|
|
30
|
+
- **AI Hints: page heading in navigation** — `navigateTo` and `openBrowser` now show page heading
|
|
31
|
+
- Extracts h1 or `.page-title` / `[class*="page-title"]` fallback for SPAs
|
|
32
|
+
- Filters sr-only/visually-hidden elements (clip, 1px size, opacity)
|
|
33
|
+
- 500ms SPA render delay for Angular/React/Vue frameworks
|
|
34
|
+
|
|
35
|
+
- **Swagger/OpenAPI tools** — `loadSwagger` and `generateApiModels` (Phase 1)
|
|
36
|
+
- `loadSwagger`: Parse OpenAPI 2.0/3.x specs from URL or file (JSON/YAML)
|
|
37
|
+
- `generateApiModels`: Generate TypeScript interfaces or Python models (dataclass/pydantic/TypedDict)
|
|
38
|
+
- $ref resolution, enum generation, snake_case conversion for Python
|
|
39
|
+
- Supports filtering specific schemas
|
|
40
|
+
|
|
41
|
+
- **Page Object Model integration in exported tests** — `pageObjectMode` parameter
|
|
42
|
+
- `generate-integrated`: Generate POM + test using it
|
|
43
|
+
- `use-existing`: Generate test referencing existing POM file
|
|
44
|
+
- Works with `exportScenarioAsCode` and `appendScenarioToFile`
|
|
45
|
+
|
|
46
|
+
- **Synthetic drag mode** — `drag` tool now supports `mode: 'synthetic'`
|
|
47
|
+
- Better compatibility with JS libraries (frappe-gantt, jQuery UI, Sortable.js)
|
|
48
|
+
- Native mode (default) for standard HTML drag operations
|
|
49
|
+
|
|
50
|
+
- **analyzePage: framework click handler detection** — Detect addEventListener-based handlers
|
|
51
|
+
- APOM IDs returned from `smartFindElement` and `findElementsByText`
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
- Swagger Phase 1 code review fixes (error handling, edge cases)
|
|
55
|
+
|
|
5
56
|
## [3.3.8] - 2026-02-03
|
|
6
57
|
|
|
7
58
|
### Added
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
## Why ChromeTools MCP?
|
|
8
8
|
|
|
9
9
|
**For AI Agents & Developers:**
|
|
10
|
-
- 🎯 **
|
|
10
|
+
- 🎯 **56 specialized tools** for browser automation - from simple clicks to Figma comparisons
|
|
11
11
|
- 🧠 **APOM (Agent Page Object Model)** - AI-friendly page representation (~8-10k tokens vs 15-25k for screenshots)
|
|
12
12
|
- 🔄 **Persistent browser sessions** - pages stay open between commands for iterative workflows
|
|
13
13
|
- ⚡ **Framework-aware** - handles React, Vue, Angular events and state updates automatically
|
|
@@ -219,7 +219,7 @@ The Chrome Extension is **required** for scenario recording and other advanced f
|
|
|
219
219
|
- [Installation](#installation)
|
|
220
220
|
- [Chrome Extension Setup](#chrome-extension-setup)
|
|
221
221
|
- [AI Optimization Features](#ai-optimization-features)- [Scenario Recorder](#scenario-recorder) - Visual UI-based recording with smart optimization
|
|
222
|
-
- [Available Tools](#available-tools) - **
|
|
222
|
+
- [Available Tools](#available-tools) - **48+ Tools Total**
|
|
223
223
|
- [AI-Powered Tools](#ai-powered-tools) - smartFindElement, analyzePage, getElementDetails, findElementsByText
|
|
224
224
|
- [Core Tools](#1-core-tools) - ping, openBrowser
|
|
225
225
|
- [Interaction Tools](#2-interaction-tools) - click, type, scrollTo, selectOption, selectFromGroup, drag, scrollHorizontal
|
|
@@ -227,6 +227,7 @@ The Chrome Extension is **required** for scenario recording and other advanced f
|
|
|
227
227
|
- [Advanced Tools](#4-advanced-tools) - executeScript, getConsoleLogs, listNetworkRequests, getNetworkRequest, filterNetworkRequests, hover, setStyles, setViewport, getViewport, navigateTo
|
|
228
228
|
- [Tab Management Tools](#5-tab-management-tools) - listTabs, switchTab
|
|
229
229
|
- [Recorder Tools](#7-recorder-tools) - enableRecorder, executeScenario, listScenarios, searchScenarios, getScenarioInfo, deleteScenario, exportScenarioAsCode, appendScenarioToFile, generatePageObject
|
|
230
|
+
- [API / Swagger Tools](#8-api--swagger-tools) - loadSwagger, generateApiModels
|
|
230
231
|
- [Typical Workflow Example](#typical-workflow-example)
|
|
231
232
|
- [Tool Usage Tips](#tool-usage-tips)
|
|
232
233
|
- [Configuration](#configuration)
|
|
@@ -260,7 +261,7 @@ AI: smartFindElement("login button")
|
|
|
260
261
|
|
|
261
262
|
1. **`analyzePage`** - 🔥 **USE FREQUENTLY** - Get current page state after loads, clicks, submissions (cached, use refresh:true)
|
|
262
263
|
2. **`smartFindElement`** - Natural language element search with multilingual support
|
|
263
|
-
3. **AI Hints** - Automatic context in all tools (page type,
|
|
264
|
+
3. **AI Hints** - Automatic context in all tools (page type, page heading, modal content, dropdown/menu items, suggestions)
|
|
264
265
|
4. **Text search** - `findElementsByText` for finding elements by visible text
|
|
265
266
|
|
|
266
267
|
**Performance:** 3-5x faster, 5-10x fewer requests
|
|
@@ -499,6 +500,10 @@ Click an element with optional result screenshot. **PREFERRED**: Use APOM ID fro
|
|
|
499
500
|
- **Use case**: Buttons, links, form submissions, Django admin forms
|
|
500
501
|
- **Returns**: Confirmation text + optional screenshot + network diagnostics
|
|
501
502
|
- **Performance**: 2-10x faster without screenshot, instant with skipNetworkWait
|
|
503
|
+
- **Click strategy**: Three-tier fallback for maximum compatibility:
|
|
504
|
+
1. Puppeteer native click (trusted CDP events)
|
|
505
|
+
2. CDP coordinate click at element center (trusted, bypasses interception check)
|
|
506
|
+
3. JavaScript `element.click()` (untrusted, last resort)
|
|
502
507
|
- **Example**:
|
|
503
508
|
```javascript
|
|
504
509
|
// PREFERRED: Using APOM ID
|
|
@@ -603,10 +608,16 @@ Drag element by mouse (click-hold-move-release). Simulates real mouse drag, not
|
|
|
603
608
|
- `direction` (required): 'up', 'down', 'left', 'right', 'up-left', 'up-right', 'down-left', 'down-right'
|
|
604
609
|
- `distance` (optional): Distance in pixels (default: 100)
|
|
605
610
|
- `duration` (optional): Drag duration in milliseconds (default: 500)
|
|
611
|
+
- `mode` (optional): 'native' (default) or 'synthetic'
|
|
612
|
+
- **'native'**: Uses Puppeteer mouse API - faster, works for most cases
|
|
613
|
+
- **'synthetic'**: Dispatches DOM events (pointerdown/pointermove/pointerup) - better compatibility with JS libraries (frappe-gantt, jQuery UI Draggable, custom drag handlers)
|
|
606
614
|
- **Use case**: Interactive maps (Google Maps, Leaflet), Gantt charts, SVG diagrams, canvas elements, sliders, drag-to-pan interfaces
|
|
607
|
-
- **How it works**:
|
|
615
|
+
- **How it works**:
|
|
616
|
+
- **Native mode**: Uses Puppeteer's mouse API (mousedown → mousemove → mouseup)
|
|
617
|
+
- **Synthetic mode**: Dispatches PointerEvent/MouseEvent on element with intermediate pointermove events during drag
|
|
618
|
+
- **When to use synthetic mode**: If native drag doesn't trigger JS library event handlers (e.g., frappe-gantt, jQuery UI, React DnD)
|
|
608
619
|
- **NOT for**: Standard overflow scrollbars (use `scrollTo` or `scrollHorizontal` instead)
|
|
609
|
-
- **Returns**: Start/end mouse positions and
|
|
620
|
+
- **Returns**: Start/end mouse positions, drag delta, and mode used
|
|
610
621
|
|
|
611
622
|
#### scrollHorizontal
|
|
612
623
|
Scroll element horizontally (for tables, carousels, wide content).
|
|
@@ -1118,17 +1129,24 @@ Delete a scenario and its associated secrets. Searches all projects to find the
|
|
|
1118
1129
|
- `language` (required): Target framework - `"playwright-typescript"`, `"playwright-python"`, `"selenium-python"`, `"selenium-java"`
|
|
1119
1130
|
- `cleanSelectors` (optional): Remove unstable CSS classes (default: true)
|
|
1120
1131
|
- `includeComments` (optional): Include descriptive comments (default: true)
|
|
1121
|
-
- `generatePageObject` (optional): Also generate Page Object class for the page (default: false)
|
|
1132
|
+
- `generatePageObject` (optional): Also generate Page Object class for the page (default: false). Legacy - use `pageObjectMode` instead.
|
|
1122
1133
|
- `pageObjectClassName` (optional): Custom Page Object class name (auto-generated if not provided)
|
|
1134
|
+
- `pageObjectMode` (optional): POM integration mode:
|
|
1135
|
+
- `"none"` (default) - no Page Object
|
|
1136
|
+
- `"generate"` - generate separate POM file (same as `generatePageObject: true`)
|
|
1137
|
+
- `"generate-integrated"` - generate POM + test that **uses** POM methods (imports, instantiates, calls POM methods)
|
|
1138
|
+
- `"use-existing"` - generate test that uses an **existing** POM file (requires `pageObjectFile`)
|
|
1139
|
+
- `pageObjectFile` (optional): Path to existing POM file (required for `"use-existing"` mode)
|
|
1123
1140
|
|
|
1124
|
-
- **Use case**: Create new test files from recorded scenarios with optional Page
|
|
1141
|
+
- **Use case**: Create new test files from recorded scenarios with optional Page Object integration
|
|
1125
1142
|
|
|
1126
1143
|
- **Returns**: JSON with:
|
|
1127
1144
|
- `action`: `"create_new_file"`
|
|
1128
1145
|
- `suggestedFileName`: Suggested test filename
|
|
1129
1146
|
- `testCode`: Full test code with imports
|
|
1130
1147
|
- `instruction`: Instructions for Claude Code
|
|
1131
|
-
- `pageObject` (if
|
|
1148
|
+
- `pageObject` (if POM generated): Page Object code and metadata
|
|
1149
|
+
- `pomIntegration` (if POM integrated): `{ className, mode }` info
|
|
1132
1150
|
|
|
1133
1151
|
- **Example 1 - Test only**:
|
|
1134
1152
|
```javascript
|
|
@@ -1147,29 +1165,46 @@ Delete a scenario and its associated secrets. Searches all projects to find the
|
|
|
1147
1165
|
}
|
|
1148
1166
|
```
|
|
1149
1167
|
|
|
1150
|
-
- **Example 2 - Test + Page Object
|
|
1168
|
+
- **Example 2 - Test + separate Page Object** (legacy):
|
|
1151
1169
|
```javascript
|
|
1152
|
-
// Export with Page Object class
|
|
1153
1170
|
exportScenarioAsCode({
|
|
1154
1171
|
scenarioName: "login_test",
|
|
1155
1172
|
language: "playwright-typescript",
|
|
1156
1173
|
generatePageObject: true,
|
|
1157
1174
|
pageObjectClassName: "LoginPage"
|
|
1158
1175
|
})
|
|
1176
|
+
```
|
|
1159
1177
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
"
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
}
|
|
1178
|
+
- **Example 3 - Test + integrated Page Object** (recommended):
|
|
1179
|
+
```javascript
|
|
1180
|
+
// Generate POM and test that USES POM methods (not raw selectors)
|
|
1181
|
+
exportScenarioAsCode({
|
|
1182
|
+
scenarioName: "login_test",
|
|
1183
|
+
language: "playwright-typescript",
|
|
1184
|
+
pageObjectMode: "generate-integrated",
|
|
1185
|
+
pageObjectClassName: "LoginPage"
|
|
1186
|
+
})
|
|
1187
|
+
|
|
1188
|
+
// Returns test code using POM:
|
|
1189
|
+
// import { LoginPage } from './LoginPage';
|
|
1190
|
+
// test('login_test', async ({ page }) => {
|
|
1191
|
+
// const loginPage = new LoginPage(page);
|
|
1192
|
+
// await loginPage.goto();
|
|
1193
|
+
// await loginPage.fillUsername('admin');
|
|
1194
|
+
// await loginPage.clickLoginBtn();
|
|
1195
|
+
// });
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1198
|
+
- **Example 4 - Test using existing POM file**:
|
|
1199
|
+
```javascript
|
|
1200
|
+
// Use pre-existing Page Object file
|
|
1201
|
+
exportScenarioAsCode({
|
|
1202
|
+
scenarioName: "login_test",
|
|
1203
|
+
language: "playwright-typescript",
|
|
1204
|
+
pageObjectMode: "use-existing",
|
|
1205
|
+
pageObjectFile: "./pages/LoginPage.ts"
|
|
1206
|
+
})
|
|
1207
|
+
// Test will import and use methods from the existing LoginPage
|
|
1173
1208
|
```
|
|
1174
1209
|
|
|
1175
1210
|
- **Selector Cleaning**: Automatically removes unstable patterns:
|
|
@@ -1191,8 +1226,10 @@ Append recorded scenario as test code to an **EXISTING** test file. Automaticall
|
|
|
1191
1226
|
- `referenceTestName` (optional): Reference test name for 'before'/'after' insertion
|
|
1192
1227
|
- `cleanSelectors` (optional): Remove unstable CSS classes (default: true)
|
|
1193
1228
|
- `includeComments` (optional): Include descriptive comments (default: true)
|
|
1194
|
-
- `generatePageObject` (optional): Also generate Page Object class for the page (default: false)
|
|
1229
|
+
- `generatePageObject` (optional): Also generate Page Object class for the page (default: false). Legacy - use `pageObjectMode` instead.
|
|
1195
1230
|
- `pageObjectClassName` (optional): Custom Page Object class name (auto-generated if not provided)
|
|
1231
|
+
- `pageObjectMode` (optional): POM integration mode - `"none"`, `"generate"`, `"generate-integrated"`, `"use-existing"` (see exportScenarioAsCode for details)
|
|
1232
|
+
- `pageObjectFile` (optional): Path to existing POM file (required for `"use-existing"` mode)
|
|
1196
1233
|
|
|
1197
1234
|
- **Use case**: Add tests to existing test files without overwriting current tests
|
|
1198
1235
|
|
|
@@ -1325,6 +1362,78 @@ Append recorded scenario as test code to an **EXISTING** test file. Automaticall
|
|
|
1325
1362
|
- `selenium-python`: Selenium with Python (WebDriver, explicit waits, By locators)
|
|
1326
1363
|
- `selenium-java`: Selenium with Java (WebDriver, Page Factory compatible)
|
|
1327
1364
|
|
|
1365
|
+
### 8. API / Swagger Tools
|
|
1366
|
+
|
|
1367
|
+
Tools for loading OpenAPI/Swagger specs and generating typed API models.
|
|
1368
|
+
|
|
1369
|
+
#### `loadSwagger`
|
|
1370
|
+
|
|
1371
|
+
Parse an OpenAPI 2.0 (Swagger) or 3.x spec and return a structured summary of endpoints, schemas, and auth.
|
|
1372
|
+
|
|
1373
|
+
| Parameter | Type | Required | Description |
|
|
1374
|
+
|-----------|------|----------|-------------|
|
|
1375
|
+
| `source` | string | Yes | URL (`https://...`) or local file path to `swagger.json` / `openapi.yaml` |
|
|
1376
|
+
| `format` | `'auto'` \| `'json'` \| `'yaml'` | No | Parse format (default: `auto` — detects from content) |
|
|
1377
|
+
|
|
1378
|
+
**Response includes:**
|
|
1379
|
+
- API title, version, base URL
|
|
1380
|
+
- All endpoints with method, path, operationId, parameters, request body, responses
|
|
1381
|
+
- Schema summaries (property names, types, enums)
|
|
1382
|
+
- Auth schemes (Bearer, API key, OAuth2)
|
|
1383
|
+
|
|
1384
|
+
```javascript
|
|
1385
|
+
// Load from URL
|
|
1386
|
+
loadSwagger({ source: "https://petstore.swagger.io/v2/swagger.json" })
|
|
1387
|
+
|
|
1388
|
+
// Load from local file
|
|
1389
|
+
loadSwagger({ source: "/path/to/openapi.yaml" })
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
#### `generateApiModels`
|
|
1393
|
+
|
|
1394
|
+
Generate TypeScript interfaces or Python dataclasses/pydantic models from an OpenAPI spec.
|
|
1395
|
+
|
|
1396
|
+
| Parameter | Type | Required | Description |
|
|
1397
|
+
|-----------|------|----------|-------------|
|
|
1398
|
+
| `source` | string | Yes | URL or file path to spec |
|
|
1399
|
+
| `language` | `'typescript'` \| `'python'` | Yes | Target language |
|
|
1400
|
+
| `format` | `'auto'` \| `'json'` \| `'yaml'` | No | Parse format (default: `auto`) |
|
|
1401
|
+
| `style` | `'interface'` \| `'type'` | No | TypeScript style (default: `interface`) |
|
|
1402
|
+
| `pythonStyle` | `'dataclass'` \| `'pydantic'` \| `'typeddict'` | No | Python style (default: `dataclass`) |
|
|
1403
|
+
| `includeEnums` | boolean | No | Generate enum types (default: `true`) |
|
|
1404
|
+
| `schemas` | string[] | No | Filter to specific schema names |
|
|
1405
|
+
|
|
1406
|
+
**Features:**
|
|
1407
|
+
- Topological sort ensures correct declaration order
|
|
1408
|
+
- Enum deduplication (property enums reuse top-level enums)
|
|
1409
|
+
- `allOf` → extends/inheritance, `oneOf`/`anyOf` → union types
|
|
1410
|
+
- Circular reference detection with forward references
|
|
1411
|
+
- Swagger 2.0 automatically normalized to OpenAPI 3.x
|
|
1412
|
+
|
|
1413
|
+
```javascript
|
|
1414
|
+
// Generate TypeScript interfaces
|
|
1415
|
+
generateApiModels({
|
|
1416
|
+
source: "https://petstore.swagger.io/v2/swagger.json",
|
|
1417
|
+
language: "typescript"
|
|
1418
|
+
})
|
|
1419
|
+
// Returns: { code: "export interface Pet { ... }", suggestedFileName: "pet-store-api.models.ts" }
|
|
1420
|
+
|
|
1421
|
+
// Generate Python pydantic models
|
|
1422
|
+
generateApiModels({
|
|
1423
|
+
source: "/path/to/openapi.yaml",
|
|
1424
|
+
language: "python",
|
|
1425
|
+
pythonStyle: "pydantic"
|
|
1426
|
+
})
|
|
1427
|
+
// Returns: { code: "class Pet(BaseModel): ...", suggestedFileName: "pet_store_api_models.py" }
|
|
1428
|
+
|
|
1429
|
+
// Generate only specific schemas
|
|
1430
|
+
generateApiModels({
|
|
1431
|
+
source: "https://api.example.com/openapi.json",
|
|
1432
|
+
language: "typescript",
|
|
1433
|
+
schemas: ["User", "Order"]
|
|
1434
|
+
})
|
|
1435
|
+
```
|
|
1436
|
+
|
|
1328
1437
|
---
|
|
1329
1438
|
|
|
1330
1439
|
## Typical Workflow Example
|
|
@@ -1817,6 +1926,32 @@ npx chrometools-mcp --install-bridge
|
|
|
1817
1926
|
- Close and reopen Chrome
|
|
1818
1927
|
- Check Extension Service Worker console for errors
|
|
1819
1928
|
|
|
1929
|
+
## Known Limitations
|
|
1930
|
+
|
|
1931
|
+
### Angular *ngFor with Dynamic Bindings
|
|
1932
|
+
|
|
1933
|
+
In Angular apps using Zone.js, **any** programmatic click (including CDP trusted events) can trigger change detection **between** event listener callbacks. If `*ngFor` iterates over a getter that returns a new array reference each time (e.g., `[options]="getOptions()"`), Angular destroys and recreates all child elements mid-dispatch, causing `@HostListener('click')` on the target element to never fire. Only real hardware mouse events (physical mouse) are immune — CDP events, despite being `isTrusted: true`, are not dispatched through the OS event queue.
|
|
1934
|
+
|
|
1935
|
+
ChromeTools **automatically detects** this: after each click, it checks if the target element was removed from DOM. If so, the `ELEMENT DETACHED` hint is shown with a workaround guide.
|
|
1936
|
+
|
|
1937
|
+
**App fix** (recommended): add `trackBy` to `*ngFor`, or cache the array reference instead of returning a new one each time.
|
|
1938
|
+
|
|
1939
|
+
**Workaround** when app fix is not possible — use `executeScript` to call the Angular component API directly:
|
|
1940
|
+
```javascript
|
|
1941
|
+
// 1. Find the component instance
|
|
1942
|
+
executeScript({ script: `
|
|
1943
|
+
const comp = ng.getComponent(document.querySelector('my-component'));
|
|
1944
|
+
// 2. Explore available events
|
|
1945
|
+
Object.keys(comp).filter(k => k.includes('Event'));
|
|
1946
|
+
` })
|
|
1947
|
+
|
|
1948
|
+
// 3. Emit the event directly (bypasses DOM click entirely)
|
|
1949
|
+
executeScript({ script: `
|
|
1950
|
+
const comp = ng.getComponent(document.querySelector('my-component'));
|
|
1951
|
+
comp.selectedOptionChangeEvent.emit(comp.options.find(o => o.name === 'Delete'));
|
|
1952
|
+
` })
|
|
1953
|
+
```
|
|
1954
|
+
|
|
1820
1955
|
## Architecture
|
|
1821
1956
|
|
|
1822
1957
|
- **Puppeteer** for Chrome automation
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Спецификация: Интеграция Page Object Model в экспортируемые тесты
|
|
2
|
+
|
|
3
|
+
## Контекст
|
|
4
|
+
|
|
5
|
+
QA-автоматизаторы жалуются, что тесты, экспортируемые через `exportScenarioAsCode` / `appendScenarioToFile`, **не используют Page Object**. Даже при `generatePageObject: true` генерируются два несвязанных файла — тест с raw-селекторами и POM-класс отдельно.
|
|
6
|
+
|
|
7
|
+
**Сейчас** (raw-селекторы):
|
|
8
|
+
```typescript
|
|
9
|
+
test('login', async ({ page }) => {
|
|
10
|
+
await page.goto('https://example.com/login');
|
|
11
|
+
await page.locator('#username').fill('admin');
|
|
12
|
+
await page.locator('#password').fill('secret');
|
|
13
|
+
await page.locator('#login-btn').click();
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Нужно** (POM-интеграция):
|
|
18
|
+
```typescript
|
|
19
|
+
import { LoginPage } from './LoginPage';
|
|
20
|
+
|
|
21
|
+
test('login', async ({ page }) => {
|
|
22
|
+
const loginPage = new LoginPage(page);
|
|
23
|
+
await loginPage.goto();
|
|
24
|
+
await loginPage.fillUsername('admin');
|
|
25
|
+
await loginPage.fillPassword('secret');
|
|
26
|
+
await loginPage.clickLoginBtn();
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Подход
|
|
31
|
+
|
|
32
|
+
Добавить новый параметр `pageObjectMode` к тулам экспорта. Два сценария:
|
|
33
|
+
|
|
34
|
+
1. **`generate-integrated`** — сгенерировать POM + тест, который его использует
|
|
35
|
+
2. **`use-existing`** — прочитать существующий POM-файл, сгенерировать тест, который его использует
|
|
36
|
+
|
|
37
|
+
Ключевой механизм — **selector matching**: сопоставление селекторов из записанного сценария с локаторами POM-элементов.
|
|
38
|
+
|
|
39
|
+
## Изменения по файлам
|
|
40
|
+
|
|
41
|
+
### 1. Новый файл: `utils/code-generators/pom-integrator.js`
|
|
42
|
+
|
|
43
|
+
Модуль связки POM с экшнами сценария:
|
|
44
|
+
|
|
45
|
+
- **`matchActionToPomElement(actionSelector, pomElements)`** — сопоставляет селектор экшна с элементом POM:
|
|
46
|
+
1. Exact match (`#username` === `#username`)
|
|
47
|
+
2. Normalized match (strip tag prefix: `input#username` → `#username`)
|
|
48
|
+
3. Key-based match (извлечь id/name/data-testid и сравнить значения)
|
|
49
|
+
4. Не найдено → null (fallback на raw-селектор)
|
|
50
|
+
|
|
51
|
+
- **`parsePomFile(fileContent, framework)`** — парсит существующий POM-файл regex'ами, возвращает `{ className, elements: [{name, selector, methodName, methodType}] }`:
|
|
52
|
+
- Playwright TS: ищет `this.X = page.locator('...')` и `async fillX(` / `async clickX(`
|
|
53
|
+
- Playwright Python: ищет `self.X = page.locator('...')` и `def fill_X(` / `def click_X(`
|
|
54
|
+
- Selenium Python: ищет `X = (By.CSS_SELECTOR, '...')` и `def fill_X(` / `def click_X(`
|
|
55
|
+
- Selenium Java: ищет `By X = By.cssSelector("...")` и `void fillX(` / `void clickX(`
|
|
56
|
+
|
|
57
|
+
### 2. Изменить: `recorder/page-object-generator.js`
|
|
58
|
+
|
|
59
|
+
Добавить в return `generatePageObject()` поле **`elements`** — массив структурированных метаданных:
|
|
60
|
+
```js
|
|
61
|
+
elements: uniqueElements.map(el => ({
|
|
62
|
+
name: sanitizeIdentifier(el.name, lang),
|
|
63
|
+
selector: el.selector,
|
|
64
|
+
tag: el.tag,
|
|
65
|
+
type: el.type,
|
|
66
|
+
methodName: generateMethodName(el, framework), // "fillUsername" / "clickSubmit"
|
|
67
|
+
methodType: getMethodType(el) // "fill" | "click" | "select"
|
|
68
|
+
}))
|
|
69
|
+
```
|
|
70
|
+
Добавить вспомогательные функции `generateMethodName()` и `getMethodType()` (логика уже есть в `generateActionMethods()`, нужно её вынести).
|
|
71
|
+
|
|
72
|
+
### 3. Изменить: `utils/code-generators/code-generator-base.js`
|
|
73
|
+
|
|
74
|
+
Добавить в конструктор опции `pomElements`, `pomClassName`, `pomImportPath`.
|
|
75
|
+
|
|
76
|
+
Добавить методы с дефолтными реализациями (no-op):
|
|
77
|
+
- `generatePomImports(className, importPath)` → `[]`
|
|
78
|
+
- `generatePomInstantiation(className)` → `[]`
|
|
79
|
+
- `generatePomAction(action, pomElement)` → `null` (значит fallback)
|
|
80
|
+
|
|
81
|
+
Модифицировать `generate()` и `generateTestOnly()`:
|
|
82
|
+
```js
|
|
83
|
+
// После generateImports(), если POM mode:
|
|
84
|
+
if (this.options.pomClassName) {
|
|
85
|
+
lines.push(...this.generatePomImports(this.options.pomClassName, this.options.pomImportPath));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// После generateTestHeader(), если POM mode:
|
|
89
|
+
if (this.options.pomClassName) {
|
|
90
|
+
lines.push(...this.generatePomInstantiation(this.options.pomClassName));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// В цикле экшнов:
|
|
94
|
+
for (const action of scenario.chain) {
|
|
95
|
+
let actionCode = null;
|
|
96
|
+
if (this.options.pomElements) {
|
|
97
|
+
const match = matchActionToPomElement(this.prepareSelector(action), this.options.pomElements);
|
|
98
|
+
if (match) {
|
|
99
|
+
actionCode = this.generatePomAction(action, match);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!actionCode) {
|
|
103
|
+
actionCode = this.generateAction(action);
|
|
104
|
+
}
|
|
105
|
+
if (actionCode?.length > 0) lines.push(...actionCode);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Специальная обработка `navigate` → если URL совпадает с POM entryUrl, заменить на `pomInstance.goto()`.
|
|
110
|
+
|
|
111
|
+
### 4. Изменить: `playwright-typescript.js`
|
|
112
|
+
|
|
113
|
+
Реализовать 3 POM-метода:
|
|
114
|
+
```js
|
|
115
|
+
generatePomImports(className, importPath) {
|
|
116
|
+
return [`import { ${className} } from '${importPath || './' + className}';`];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
generatePomInstantiation(className) {
|
|
120
|
+
const varName = className.charAt(0).toLowerCase() + className.slice(1);
|
|
121
|
+
return [this.indent(`const ${varName} = new ${className}(page);`), ''];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
generatePomAction(action, pomElement) {
|
|
125
|
+
const varName = this.options.pomClassName.charAt(0).toLowerCase() + this.options.pomClassName.slice(1);
|
|
126
|
+
// fill → await varName.fillUsername('text');
|
|
127
|
+
// click → await varName.clickSubmit();
|
|
128
|
+
// select → await varName.selectCountry('US');
|
|
129
|
+
// other → await varName.elementName.hover();
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 5. Изменить: `playwright-python.js`
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# Import: from login_page import LoginPage
|
|
137
|
+
# Instantiation: login_page = LoginPage(page)
|
|
138
|
+
# Fill: login_page.fill_username('text')
|
|
139
|
+
# Click: login_page.click_submit()
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 6. Изменить: `selenium-python.js`
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
# Import: from login_page import LoginPage
|
|
146
|
+
# Instantiation: login_page = LoginPage(driver)
|
|
147
|
+
# Fill: login_page.fill_username('text')
|
|
148
|
+
# Click: login_page.click_submit()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 7. Изменить: `selenium-java.js`
|
|
152
|
+
|
|
153
|
+
```java
|
|
154
|
+
// Import: import pages.LoginPage;
|
|
155
|
+
// Instantiation: LoginPage loginPage = new LoginPage(driver);
|
|
156
|
+
// Fill: loginPage.fillUsername("text");
|
|
157
|
+
// Click: loginPage.clickSubmit();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 8. Изменить: `server/tool-schemas.js`
|
|
161
|
+
|
|
162
|
+
Добавить к `ExportScenarioAsCodeSchema` и `AppendScenarioToFileSchema`:
|
|
163
|
+
```js
|
|
164
|
+
pageObjectMode: z.enum(['none', 'generate', 'generate-integrated', 'use-existing']).optional()
|
|
165
|
+
.describe("POM integration: 'none' (default), 'generate' (separate POM, current behavior), 'generate-integrated' (POM + test using it), 'use-existing' (test uses existing POM file)"),
|
|
166
|
+
pageObjectFile: z.string().optional()
|
|
167
|
+
.describe("Path to existing POM file (for 'use-existing' mode)"),
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 9. Изменить: `server/tool-definitions.js`
|
|
171
|
+
|
|
172
|
+
Обновить описания тулов `exportScenarioAsCode` и `appendScenarioToFile` — добавить `pageObjectMode` и `pageObjectFile` в `inputSchema.properties`.
|
|
173
|
+
|
|
174
|
+
### 10. Изменить: `index.js` (экспорт-хендлеры)
|
|
175
|
+
|
|
176
|
+
**`exportScenarioAsCode` (~строка 3321):**
|
|
177
|
+
- При `pageObjectMode === 'generate-integrated'`: сгенерировать POM, передать `pomElements`/`pomClassName` в генератор → получить тест с POM-вызовами
|
|
178
|
+
- При `pageObjectMode === 'use-existing'`: прочитать POM-файл через `FileAppender.readFile()`, распарсить через `parsePomFile()`, передать в генератор
|
|
179
|
+
- При `pageObjectMode === 'generate'` или `generatePageObject: true`: текущее поведение (обратная совместимость)
|
|
180
|
+
|
|
181
|
+
**`appendScenarioToFile` (~строка 3182):**
|
|
182
|
+
- Аналогичная логика для `generateTestOnly()`
|
|
183
|
+
|
|
184
|
+
### 11. Обновить: `README.md`
|
|
185
|
+
|
|
186
|
+
Документировать `pageObjectMode` и `pageObjectFile` параметры для обеих тул.
|
|
187
|
+
|
|
188
|
+
## Обратная совместимость
|
|
189
|
+
|
|
190
|
+
- `generatePageObject: true` (bool) — работает как раньше (maps to `pageObjectMode: 'generate'`)
|
|
191
|
+
- Новое поведение только при явном `pageObjectMode: 'generate-integrated'` или `'use-existing'`
|
|
192
|
+
- Fallback: если экшн не сматчился с POM-элементом — raw-селектор (как сейчас)
|
|
193
|
+
|
|
194
|
+
## Алгоритм Selector Matching
|
|
195
|
+
|
|
196
|
+
| Action selector | POM selector | Результат |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `#username` | `#username` | Exact match |
|
|
199
|
+
| `input#username` | `#username` | Normalized (strip tag) |
|
|
200
|
+
| `[name="email"]` | `[name="email"]` | Exact match |
|
|
201
|
+
| `input[name="email"]` | `[name="email"]` | Key match (name=email) |
|
|
202
|
+
| `.complex > path:nth(2)` | `#username` | No match → raw fallback |
|
|
203
|
+
|
|
204
|
+
## Порядок реализации
|
|
205
|
+
|
|
206
|
+
1. `page-object-generator.js` — добавить `elements` metadata + helper-функции
|
|
207
|
+
2. `pom-integrator.js` — создать (matching + parsing)
|
|
208
|
+
3. `code-generator-base.js` — POM-методы + модифицированный generate-цикл
|
|
209
|
+
4. `playwright-typescript.js` — имплементация POM-методов (reference)
|
|
210
|
+
5. `playwright-python.js` — имплементация POM-методов
|
|
211
|
+
6. `selenium-python.js` — имплементация POM-методов
|
|
212
|
+
7. `selenium-java.js` — имплементация POM-методов
|
|
213
|
+
8. `tool-schemas.js` — новые параметры
|
|
214
|
+
9. `tool-definitions.js` — обновить descriptions + schema
|
|
215
|
+
10. `index.js` — оркестрация в хендлерах экспорта
|
|
216
|
+
11. `README.md` — документация
|
|
217
|
+
|
|
218
|
+
## Верификация
|
|
219
|
+
|
|
220
|
+
1. Записать сценарий на тестовой странице (например, Google Search)
|
|
221
|
+
2. Вызвать `exportScenarioAsCode` с `pageObjectMode: 'generate-integrated'`
|
|
222
|
+
3. Проверить что тест-файл содержит import POM-класса и вызывает его методы
|
|
223
|
+
4. Вызвать `generatePageObject`, сохранить файл, затем `exportScenarioAsCode` с `pageObjectMode: 'use-existing'`
|
|
224
|
+
5. Проверить что тест использует существующий POM
|
|
225
|
+
6. Проверить fallback: экшны без матча → raw-селекторы
|
|
226
|
+
7. Проверить все 4 фреймворка
|
|
227
|
+
8. Проверить `appendScenarioToFile` с POM-интеграцией
|