@mechris3/glassbox 0.1.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/README.md +213 -0
- package/dist/bin/glassbox.d.ts +14 -0
- package/dist/bin/glassbox.d.ts.map +1 -0
- package/dist/bin/glassbox.js +140 -0
- package/dist/bin/glassbox.js.map +1 -0
- package/dist/src/adapters/base-adapter.d.ts +70 -0
- package/dist/src/adapters/base-adapter.d.ts.map +1 -0
- package/dist/src/adapters/base-adapter.js +155 -0
- package/dist/src/adapters/base-adapter.js.map +1 -0
- package/dist/src/adapters/browser-adapter.interface.d.ts +41 -0
- package/dist/src/adapters/browser-adapter.interface.d.ts.map +1 -0
- package/dist/src/adapters/browser-adapter.interface.js +7 -0
- package/dist/src/adapters/browser-adapter.interface.js.map +1 -0
- package/dist/src/adapters/cdp-adapter.d.ts +63 -0
- package/dist/src/adapters/cdp-adapter.d.ts.map +1 -0
- package/dist/src/adapters/cdp-adapter.js +231 -0
- package/dist/src/adapters/cdp-adapter.js.map +1 -0
- package/dist/src/ai/collectors.d.ts +35 -0
- package/dist/src/ai/collectors.d.ts.map +1 -0
- package/dist/src/ai/collectors.js +170 -0
- package/dist/src/ai/collectors.js.map +1 -0
- package/dist/src/ai/queries.d.ts +21 -0
- package/dist/src/ai/queries.d.ts.map +1 -0
- package/dist/src/ai/queries.js +59 -0
- package/dist/src/ai/queries.js.map +1 -0
- package/dist/src/ai/types.d.ts +55 -0
- package/dist/src/ai/types.d.ts.map +1 -0
- package/dist/src/ai/types.js +6 -0
- package/dist/src/ai/types.js.map +1 -0
- package/dist/src/cdp/connection.d.ts +37 -0
- package/dist/src/cdp/connection.d.ts.map +1 -0
- package/dist/src/cdp/connection.js +86 -0
- package/dist/src/cdp/connection.js.map +1 -0
- package/dist/src/cdp/connection.test.d.ts +6 -0
- package/dist/src/cdp/connection.test.d.ts.map +1 -0
- package/dist/src/cdp/connection.test.js +93 -0
- package/dist/src/cdp/connection.test.js.map +1 -0
- package/dist/src/cdp/domains.d.ts +116 -0
- package/dist/src/cdp/domains.d.ts.map +1 -0
- package/dist/src/cdp/domains.js +153 -0
- package/dist/src/cdp/domains.js.map +1 -0
- package/dist/src/cdp/launcher.d.ts +26 -0
- package/dist/src/cdp/launcher.d.ts.map +1 -0
- package/dist/src/cdp/launcher.js +175 -0
- package/dist/src/cdp/launcher.js.map +1 -0
- package/dist/src/config.d.ts +28 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +72 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/config.test.d.ts +5 -0
- package/dist/src/config.test.d.ts.map +1 -0
- package/dist/src/config.test.js +46 -0
- package/dist/src/config.test.js.map +1 -0
- package/dist/src/errors.d.ts +26 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +27 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +28 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/journey.d.ts +35 -0
- package/dist/src/journey.d.ts.map +1 -0
- package/dist/src/journey.js +39 -0
- package/dist/src/journey.js.map +1 -0
- package/dist/src/mcp/server.d.ts +22 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +229 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/page-objects/base.page.d.ts +28 -0
- package/dist/src/page-objects/base.page.d.ts.map +1 -0
- package/dist/src/page-objects/base.page.js +51 -0
- package/dist/src/page-objects/base.page.js.map +1 -0
- package/dist/src/page-objects/browser-context.page.d.ts +14 -0
- package/dist/src/page-objects/browser-context.page.d.ts.map +1 -0
- package/dist/src/page-objects/browser-context.page.js +28 -0
- package/dist/src/page-objects/browser-context.page.js.map +1 -0
- package/dist/src/runner/lifecycle-hooks.d.ts +43 -0
- package/dist/src/runner/lifecycle-hooks.d.ts.map +1 -0
- package/dist/src/runner/lifecycle-hooks.js +70 -0
- package/dist/src/runner/lifecycle-hooks.js.map +1 -0
- package/dist/src/runner/run-all-journeys.d.ts +35 -0
- package/dist/src/runner/run-all-journeys.d.ts.map +1 -0
- package/dist/src/runner/run-all-journeys.js +167 -0
- package/dist/src/runner/run-all-journeys.js.map +1 -0
- package/dist/src/runner/run-journey.d.ts +17 -0
- package/dist/src/runner/run-journey.d.ts.map +1 -0
- package/dist/src/runner/run-journey.js +179 -0
- package/dist/src/runner/run-journey.js.map +1 -0
- package/dist/src/runner/spawn-journey.d.ts +36 -0
- package/dist/src/runner/spawn-journey.d.ts.map +1 -0
- package/dist/src/runner/spawn-journey.js +40 -0
- package/dist/src/runner/spawn-journey.js.map +1 -0
- package/dist/src/server/diagnostic-service.d.ts +69 -0
- package/dist/src/server/diagnostic-service.d.ts.map +1 -0
- package/dist/src/server/diagnostic-service.js +165 -0
- package/dist/src/server/diagnostic-service.js.map +1 -0
- package/dist/src/server/diagnostic-service.test.d.ts +5 -0
- package/dist/src/server/diagnostic-service.test.d.ts.map +1 -0
- package/dist/src/server/diagnostic-service.test.js +82 -0
- package/dist/src/server/diagnostic-service.test.js.map +1 -0
- package/dist/src/server/index.d.ts +11 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +167 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/journey-discovery.d.ts +16 -0
- package/dist/src/server/journey-discovery.d.ts.map +1 -0
- package/dist/src/server/journey-discovery.js +38 -0
- package/dist/src/server/journey-discovery.js.map +1 -0
- package/dist/src/server/settings-storage.d.ts +27 -0
- package/dist/src/server/settings-storage.d.ts.map +1 -0
- package/dist/src/server/settings-storage.js +27 -0
- package/dist/src/server/settings-storage.js.map +1 -0
- package/dist/src/server/test-executor.d.ts +64 -0
- package/dist/src/server/test-executor.d.ts.map +1 -0
- package/dist/src/server/test-executor.js +279 -0
- package/dist/src/server/test-executor.js.map +1 -0
- package/dist/src/server/websocket.d.ts +51 -0
- package/dist/src/server/websocket.d.ts.map +1 -0
- package/dist/src/server/websocket.js +28 -0
- package/dist/src/server/websocket.js.map +1 -0
- package/dist/src/utils/browser-discovery.d.ts +11 -0
- package/dist/src/utils/browser-discovery.d.ts.map +1 -0
- package/dist/src/utils/browser-discovery.js +102 -0
- package/dist/src/utils/browser-discovery.js.map +1 -0
- package/dist/src/utils/error-formatter.d.ts +13 -0
- package/dist/src/utils/error-formatter.d.ts.map +1 -0
- package/dist/src/utils/error-formatter.js +29 -0
- package/dist/src/utils/error-formatter.js.map +1 -0
- package/dist/src/utils/escape.d.ts +10 -0
- package/dist/src/utils/escape.d.ts.map +1 -0
- package/dist/src/utils/escape.js +22 -0
- package/dist/src/utils/escape.js.map +1 -0
- package/dist/src/utils/escape.test.d.ts +5 -0
- package/dist/src/utils/escape.test.d.ts.map +1 -0
- package/dist/src/utils/escape.test.js +49 -0
- package/dist/src/utils/escape.test.js.map +1 -0
- package/dist/src/utils/page-object-error.d.ts +5 -0
- package/dist/src/utils/page-object-error.d.ts.map +1 -0
- package/dist/src/utils/page-object-error.js +22 -0
- package/dist/src/utils/page-object-error.js.map +1 -0
- package/dist/src/utils/profile-discovery.d.ts +20 -0
- package/dist/src/utils/profile-discovery.d.ts.map +1 -0
- package/dist/src/utils/profile-discovery.js +68 -0
- package/dist/src/utils/profile-discovery.js.map +1 -0
- package/dist/src/utils/wait-utils.d.ts +10 -0
- package/dist/src/utils/wait-utils.d.ts.map +1 -0
- package/dist/src/utils/wait-utils.js +20 -0
- package/dist/src/utils/wait-utils.js.map +1 -0
- package/dist/src/utils/wait-utils.test.d.ts +5 -0
- package/dist/src/utils/wait-utils.test.d.ts.map +1 -0
- package/dist/src/utils/wait-utils.test.js +30 -0
- package/dist/src/utils/wait-utils.test.js.map +1 -0
- package/package.json +60 -0
- package/src/server/static/index.html +541 -0
- package/ui/dist/3rdpartylicenses.txt +419 -0
- package/ui/dist/browser/assets/logo.svg +29 -0
- package/ui/dist/browser/index.html +13 -0
- package/ui/dist/browser/main-7OJWN7GZ.js +16 -0
- package/ui/dist/browser/polyfills-5CFQRCPP.js +2 -0
- package/ui/dist/browser/styles-HMZYQRBV.css +1 -0
- package/ui/dist/prerendered-routes.json +3 -0
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# @mechris3/glassbox
|
|
2
|
+
|
|
3
|
+
Debugger-style browser automation test runner using Chrome DevTools Protocol (CDP) directly. No Puppeteer, no Playwright — just raw WebSocket + JSON.
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Live execution control: breakpoints, pause/resume, step-through
|
|
7
|
+
- Visual dashboard with real-time action streaming
|
|
8
|
+
- Headless batch runner for CI
|
|
9
|
+
- Diagnostic data collection (console errors, network, performance)
|
|
10
|
+
- MCP server for AI agent integration
|
|
11
|
+
- Browser and profile auto-detection
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Install
|
|
17
|
+
npm install @mechris3/glassbox
|
|
18
|
+
|
|
19
|
+
# Scaffold a project (creates glassbox.config.ts + directories)
|
|
20
|
+
npx glassbox init
|
|
21
|
+
|
|
22
|
+
# Write journeys and page objects (see below), then:
|
|
23
|
+
npx glassbox serve # Dashboard UI at http://localhost:3001
|
|
24
|
+
npx glassbox run # Headless batch run (for CI)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`glassbox init` creates this structure in your current directory:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
my-tests/
|
|
31
|
+
├── glassbox.config.ts ← created by init
|
|
32
|
+
├── journeys/ ← put your journey files here
|
|
33
|
+
└── page-objects/ ← put your page objects here
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You can set this up as a standalone project or as a subfolder within an existing frontend project:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
# Standalone
|
|
40
|
+
my-e2e-tests/
|
|
41
|
+
├── package.json
|
|
42
|
+
├── glassbox.config.ts
|
|
43
|
+
├── journeys/
|
|
44
|
+
└── page-objects/
|
|
45
|
+
|
|
46
|
+
# Inside an existing project
|
|
47
|
+
my-app/
|
|
48
|
+
├── src/
|
|
49
|
+
├── package.json
|
|
50
|
+
└── e2e/
|
|
51
|
+
├── glassbox.config.ts
|
|
52
|
+
├── journeys/
|
|
53
|
+
└── page-objects/
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Either way, run `glassbox` commands from the directory containing your `glassbox.config.ts`.
|
|
57
|
+
|
|
58
|
+
## Prerequisites
|
|
59
|
+
|
|
60
|
+
- Node.js 20+
|
|
61
|
+
- A Chromium-based browser (Chrome, Brave, Edge)
|
|
62
|
+
|
|
63
|
+
## Consumer Usage
|
|
64
|
+
|
|
65
|
+
### Page Objects
|
|
66
|
+
|
|
67
|
+
Extend `BasePage` — no constructor needed:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// page-objects/login.page.ts
|
|
71
|
+
import { BasePage } from '@mechris3/glassbox';
|
|
72
|
+
|
|
73
|
+
export class LoginPage extends BasePage {
|
|
74
|
+
private selectors = {
|
|
75
|
+
username: '[data-testid="username"]',
|
|
76
|
+
password: '[data-testid="password"]',
|
|
77
|
+
submit: '[data-testid="submit"]',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
async login(user: string, pass: string) {
|
|
81
|
+
await this.fill(this.selectors.username, user);
|
|
82
|
+
await this.fill(this.selectors.password, pass);
|
|
83
|
+
await this.click(this.selectors.submit);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async verifyLoggedIn(expectedName: string) {
|
|
87
|
+
const name = await this.getText('[data-testid="display-name"]');
|
|
88
|
+
if (name !== expectedName) throw new Error(`Expected "${expectedName}", got "${name}"`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Journeys
|
|
94
|
+
|
|
95
|
+
Extend `Journey` — use `this.page()` to create page objects:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// journeys/login.journey.ts
|
|
99
|
+
import { Journey } from '@mechris3/glassbox';
|
|
100
|
+
import { LoginPage } from '../page-objects/login.page';
|
|
101
|
+
|
|
102
|
+
export class LoginJourney extends Journey {
|
|
103
|
+
loginPage = this.page(LoginPage);
|
|
104
|
+
|
|
105
|
+
async execute() {
|
|
106
|
+
await this.loginPage.login('testuser', 'password123');
|
|
107
|
+
await this.loginPage.verifyLoggedIn('testuser');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Config
|
|
113
|
+
|
|
114
|
+
All paths in the config are relative to the config file's directory:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
my-tests/
|
|
118
|
+
├── glassbox.config.ts
|
|
119
|
+
├── journeys/
|
|
120
|
+
│ └── login.journey.ts
|
|
121
|
+
├── page-objects/
|
|
122
|
+
│ └── login.page.ts
|
|
123
|
+
└── helpers/
|
|
124
|
+
└── before-each.ts
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// glassbox.config.ts
|
|
129
|
+
import { defineConfig } from '@mechris3/glassbox';
|
|
130
|
+
|
|
131
|
+
export default defineConfig({
|
|
132
|
+
journeys: './journeys/**/*.journey.ts',
|
|
133
|
+
browser: {
|
|
134
|
+
defaultViewport: { width: 1280, height: 720 },
|
|
135
|
+
headless: false,
|
|
136
|
+
},
|
|
137
|
+
server: { port: 3001 },
|
|
138
|
+
testData: {
|
|
139
|
+
beforeEach: './helpers/before-each.ts',
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## CLI Commands
|
|
145
|
+
|
|
146
|
+
Run all commands from the directory containing your `glassbox.config.ts`:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
cd my-tests/ # where your glassbox.config.ts lives
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Dashboard (visual UI):**
|
|
153
|
+
```bash
|
|
154
|
+
npx glassbox serve # Start dashboard at http://localhost:3001
|
|
155
|
+
```
|
|
156
|
+
Open the URL in your browser to see journeys, run them, set breakpoints, pause/step, and inspect results visually.
|
|
157
|
+
|
|
158
|
+
**Headless runner (CI / terminal):**
|
|
159
|
+
```bash
|
|
160
|
+
npx glassbox run # Run all journeys, exit 0/1
|
|
161
|
+
npx glassbox run login # Only journeys matching "login"
|
|
162
|
+
npx glassbox run --url http://localhost:3000 # Specify app URL
|
|
163
|
+
npx glassbox run --headed # Show the browser while running
|
|
164
|
+
npx glassbox run --no-fail-fast # Don't stop at first failure
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Other:**
|
|
168
|
+
```bash
|
|
169
|
+
npx glassbox init # Scaffold config + directories in current dir
|
|
170
|
+
npx glassbox mcp # Start MCP server for AI agent integration
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## MCP Server (AI Integration)
|
|
174
|
+
|
|
175
|
+
The MCP server lets AI agents (Kiro, Claude) debug failed journeys by querying diagnostic data.
|
|
176
|
+
|
|
177
|
+
Add to your `.kiro/settings/mcp.json`:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"mcpServers": {
|
|
182
|
+
"glassbox": {
|
|
183
|
+
"command": "npx",
|
|
184
|
+
"args": ["glassbox", "mcp"],
|
|
185
|
+
"env": { "GLASSBOX_URL": "http://localhost:3001" }
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Requires `glassbox serve` to be running. The AI gets these tools:
|
|
192
|
+
|
|
193
|
+
| Tool | Purpose |
|
|
194
|
+
|------|---------|
|
|
195
|
+
| `getJourneyStatus` | Summary of diagnostic counts (call first) |
|
|
196
|
+
| `getConsoleErrors` | console.error/warn with stack traces |
|
|
197
|
+
| `getExceptions` | Unhandled JS exceptions with source locations |
|
|
198
|
+
| `getNetworkFailures` | Failed HTTP requests (4xx/5xx/network errors) |
|
|
199
|
+
| `getNetworkLog` | All XHR/Fetch requests with status codes |
|
|
200
|
+
| `getTimeline` | Chronological event stream |
|
|
201
|
+
| `getPerformanceMetrics` | Heap size, DOM nodes, layout recalculations |
|
|
202
|
+
| `listJourneys` | Available journey files |
|
|
203
|
+
| `runJourney` | Run by name/ID (browser stays open on failure) |
|
|
204
|
+
| `getDomSnapshot` | Inspect page HTML or a specific element |
|
|
205
|
+
| `getScreenshot` | Capture page screenshot (base64 PNG) |
|
|
206
|
+
| `getAccessibilityTree` | Full ARIA tree for understanding page structure |
|
|
207
|
+
| `resetDiagnostics` | Clear data + close live session before re-run |
|
|
208
|
+
|
|
209
|
+
The AI can identify journeys by name (`"login"`), ID (`"login-and-add-todos"`), or full path. After a failure, the browser stays open so `getDomSnapshot` and `getScreenshot` can inspect the failed state.
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
Proprietary. All rights reserved.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Glassbox CLI entry point.
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* serve (default) — Start the dashboard server with REST API + WebSocket
|
|
7
|
+
* run [journeys] — Run journeys headless from the command line
|
|
8
|
+
* init — Scaffold a glassbox.config.ts in the current directory
|
|
9
|
+
*
|
|
10
|
+
* The CLI resolves configuration from glassbox.config.ts in the working directory
|
|
11
|
+
* (or a custom path via --config). All commands respect the same config file.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=glassbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glassbox.d.ts","sourceRoot":"","sources":["../../bin/glassbox.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Glassbox CLI entry point.
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* serve (default) — Start the dashboard server with REST API + WebSocket
|
|
8
|
+
* run [journeys] — Run journeys headless from the command line
|
|
9
|
+
* init — Scaffold a glassbox.config.ts in the current directory
|
|
10
|
+
*
|
|
11
|
+
* The CLI resolves configuration from glassbox.config.ts in the working directory
|
|
12
|
+
* (or a custom path via --config). All commands respect the same config file.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const commander_1 = require("commander");
|
|
16
|
+
const config_js_1 = require("../src/config.js");
|
|
17
|
+
const index_js_1 = require("../src/server/index.js");
|
|
18
|
+
const program = new commander_1.Command();
|
|
19
|
+
program
|
|
20
|
+
.name('glassbox')
|
|
21
|
+
.description('Debugger-style browser automation test runner with live execution control via CDP')
|
|
22
|
+
.version('0.1.0');
|
|
23
|
+
/**
|
|
24
|
+
* serve — the default command.
|
|
25
|
+
*
|
|
26
|
+
* Starts the Express server which provides:
|
|
27
|
+
* - REST API for controlling test execution (run, pause, resume, step, stop)
|
|
28
|
+
* - WebSocket for real-time action streaming to connected dashboard clients
|
|
29
|
+
* - Static file serving for the Angular dashboard UI
|
|
30
|
+
*
|
|
31
|
+
* The server discovers journeys from the glob pattern in config, launches
|
|
32
|
+
* Chrome on demand when a run is triggered, and manages the browser lifecycle.
|
|
33
|
+
*/
|
|
34
|
+
program
|
|
35
|
+
.command('serve', { isDefault: true })
|
|
36
|
+
.description('Start the UI dashboard server')
|
|
37
|
+
.option('-p, --port <port>', 'Server port (overrides config)')
|
|
38
|
+
.option('-c, --config <path>', 'Path to glassbox.config.ts')
|
|
39
|
+
.action(async (options) => {
|
|
40
|
+
const config = await (0, config_js_1.loadConfig)(options.config);
|
|
41
|
+
const port = options.port ? parseInt(options.port, 10) : config.server.port;
|
|
42
|
+
const { server } = (0, index_js_1.createGlassboxServer)(config);
|
|
43
|
+
server.listen(port, () => {
|
|
44
|
+
console.log(`\n ✦ Glassbox running at http://localhost:${port}\n`);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
/**
|
|
48
|
+
* run — headless batch execution.
|
|
49
|
+
*
|
|
50
|
+
* Runs journeys without the dashboard UI. Useful for CI pipelines or
|
|
51
|
+
* quick command-line verification. Exits with code 0 if all pass, 1 if any fail.
|
|
52
|
+
*
|
|
53
|
+
* If specific journey names are provided, only those are run.
|
|
54
|
+
* Otherwise all journeys matching the configured glob are executed.
|
|
55
|
+
*/
|
|
56
|
+
program
|
|
57
|
+
.command('run [journeys...]')
|
|
58
|
+
.description('Run journeys headless (exit 0 = all pass, 1 = any fail)')
|
|
59
|
+
.option('-c, --config <path>', 'Path to glassbox.config.ts')
|
|
60
|
+
.option('--headed', 'Run in headed mode (visible browser)')
|
|
61
|
+
.option('-u, --url <url>', 'Target URL of the app under test')
|
|
62
|
+
.option('--no-fail-fast', 'Continue running after a journey fails')
|
|
63
|
+
.action(async (journeys, options) => {
|
|
64
|
+
const config = await (0, config_js_1.loadConfig)(options.config);
|
|
65
|
+
const { runAllJourneys } = await import('../src/runner/run-all-journeys.js');
|
|
66
|
+
const { exitCode } = await runAllJourneys(config, {
|
|
67
|
+
journeyFilter: journeys,
|
|
68
|
+
headed: options.headed ?? false,
|
|
69
|
+
targetUrl: options.url ?? '',
|
|
70
|
+
noFailFast: options.failFast === false,
|
|
71
|
+
});
|
|
72
|
+
process.exit(exitCode);
|
|
73
|
+
});
|
|
74
|
+
/**
|
|
75
|
+
* mcp — start the MCP server for AI agent integration.
|
|
76
|
+
*
|
|
77
|
+
* Runs a stdio-based MCP server that exposes Glassbox diagnostic tools.
|
|
78
|
+
* AI agents (Kiro, Claude, etc.) connect via the MCP protocol and can
|
|
79
|
+
* run journeys, inspect diagnostic data, and debug failures.
|
|
80
|
+
*
|
|
81
|
+
* Requires the Glassbox server to be running (glassbox serve).
|
|
82
|
+
*/
|
|
83
|
+
program
|
|
84
|
+
.command('mcp')
|
|
85
|
+
.description('Start the MCP server for AI agent integration')
|
|
86
|
+
.option('--url <url>', 'Glassbox server URL (default: http://localhost:3001)')
|
|
87
|
+
.action(async (options) => {
|
|
88
|
+
if (options.url) {
|
|
89
|
+
process.env.GLASSBOX_URL = options.url;
|
|
90
|
+
}
|
|
91
|
+
const { startMCPServer } = await import('../src/mcp/server.js');
|
|
92
|
+
await startMCPServer();
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* init — project scaffolding.
|
|
96
|
+
*
|
|
97
|
+
* Creates a glassbox.config.ts with sensible defaults in the current directory,
|
|
98
|
+
* plus a journeys/ directory and a sample page-objects/ directory.
|
|
99
|
+
* Intended for consumers setting up Glassbox in a new project.
|
|
100
|
+
*/
|
|
101
|
+
program
|
|
102
|
+
.command('init')
|
|
103
|
+
.description('Scaffold a glassbox.config.ts in the current directory')
|
|
104
|
+
.action(async () => {
|
|
105
|
+
const { writeFileSync, mkdirSync, existsSync } = await import('fs');
|
|
106
|
+
if (existsSync('glassbox.config.ts')) {
|
|
107
|
+
console.log(' glassbox.config.ts already exists — skipping.\n');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const configContent = `import { defineConfig } from 'glassbox';
|
|
111
|
+
|
|
112
|
+
export default defineConfig({
|
|
113
|
+
journeys: './journeys/**/*.journey.ts',
|
|
114
|
+
browser: {
|
|
115
|
+
defaultViewport: { width: 1280, height: 720 },
|
|
116
|
+
headless: false,
|
|
117
|
+
},
|
|
118
|
+
server: { port: 3001 },
|
|
119
|
+
testData: {
|
|
120
|
+
// beforeEach: './helpers/before-each.ts',
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
`;
|
|
124
|
+
writeFileSync('glassbox.config.ts', configContent);
|
|
125
|
+
console.log(' ✦ Created glassbox.config.ts');
|
|
126
|
+
if (!existsSync('journeys')) {
|
|
127
|
+
mkdirSync('journeys', { recursive: true });
|
|
128
|
+
console.log(' ✦ Created journeys/ directory');
|
|
129
|
+
}
|
|
130
|
+
if (!existsSync('page-objects')) {
|
|
131
|
+
mkdirSync('page-objects', { recursive: true });
|
|
132
|
+
console.log(' ✦ Created page-objects/ directory');
|
|
133
|
+
}
|
|
134
|
+
console.log('\n Next steps:');
|
|
135
|
+
console.log(' 1. Write page objects in page-objects/');
|
|
136
|
+
console.log(' 2. Write journeys in journeys/');
|
|
137
|
+
console.log(' 3. Run: npx glassbox serve\n');
|
|
138
|
+
});
|
|
139
|
+
program.parse();
|
|
140
|
+
//# sourceMappingURL=glassbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glassbox.js","sourceRoot":"","sources":["../../bin/glassbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseAdapter — abstract class providing debugger infrastructure.
|
|
3
|
+
* Breakpoints, pause/resume, step mode, slow mode, IPC via signals + stdin.
|
|
4
|
+
*
|
|
5
|
+
* All mutable debugger state is instance-scoped (not static), making
|
|
6
|
+
* the adapter testable and safe for potential multi-instance use.
|
|
7
|
+
*/
|
|
8
|
+
import type { BrowserAdapter, DownloadResult } from './browser-adapter.interface.js';
|
|
9
|
+
interface IPCCommand {
|
|
10
|
+
command: 'pause' | 'resume' | 'step' | 'config';
|
|
11
|
+
stepsRemaining?: number;
|
|
12
|
+
actionDelay?: number;
|
|
13
|
+
breakpoints?: number[];
|
|
14
|
+
}
|
|
15
|
+
export declare abstract class BaseAdapter implements BrowserAdapter {
|
|
16
|
+
protected static downloadDir: string;
|
|
17
|
+
private isPaused;
|
|
18
|
+
private stepMode;
|
|
19
|
+
private stepsRemaining;
|
|
20
|
+
private actionIndex;
|
|
21
|
+
private breakpoints;
|
|
22
|
+
private actionDelay;
|
|
23
|
+
private baseUrl;
|
|
24
|
+
constructor();
|
|
25
|
+
abstract click(selector: string): Promise<void>;
|
|
26
|
+
abstract fill(selector: string, value: string): Promise<void>;
|
|
27
|
+
abstract type(selector: string, value: string, options?: {
|
|
28
|
+
delay?: number;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
abstract getText(selector: string): Promise<string>;
|
|
31
|
+
abstract getInputValue(selector: string): Promise<string>;
|
|
32
|
+
abstract getAttribute(selector: string, attribute: string): Promise<string | null>;
|
|
33
|
+
abstract countElements(selector: string): Promise<number>;
|
|
34
|
+
abstract isVisible(selector: string): Promise<boolean>;
|
|
35
|
+
abstract isDisabled(selector: string): Promise<boolean>;
|
|
36
|
+
abstract waitForSelector(selector: string, options?: {
|
|
37
|
+
timeout?: number;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
abstract waitForHidden(selector: string, options?: {
|
|
40
|
+
timeout?: number;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
abstract waitForTimeout(ms: number): Promise<void>;
|
|
43
|
+
abstract goto(url: string): Promise<void>;
|
|
44
|
+
abstract clickAndWaitForNavigation(selector: string): Promise<void>;
|
|
45
|
+
abstract getCurrentUrl(): Promise<string>;
|
|
46
|
+
abstract evaluate<T = unknown>(script: string | (() => T)): Promise<T>;
|
|
47
|
+
abstract clearSession(origin?: string): Promise<void>;
|
|
48
|
+
abstract readClipboard(): Promise<string>;
|
|
49
|
+
abstract uploadFile(selector: string, filePath: string): Promise<void>;
|
|
50
|
+
abstract selectByIndex(selector: string, index: number): Promise<void>;
|
|
51
|
+
abstract selectByValue(selector: string, value: string): Promise<void>;
|
|
52
|
+
abstract selectByText(selector: string, text: string, exact?: boolean): Promise<void>;
|
|
53
|
+
abstract clickAndDownload(selector: string): Promise<DownloadResult>;
|
|
54
|
+
abstract clearDownloads(): Promise<void>;
|
|
55
|
+
/** Called BEFORE each adapter action — handles breakpoints, pause state, step mode. */
|
|
56
|
+
protected logAndCheckAction(description: string): Promise<void>;
|
|
57
|
+
/** Called AFTER each adapter action. */
|
|
58
|
+
protected logActionComplete(): void;
|
|
59
|
+
/** Applies slow-mode delay between actions. */
|
|
60
|
+
protected addSlowModeDelay(): Promise<void>;
|
|
61
|
+
/** Resolve relative URLs against the base URL (TEST_DOMAIN). */
|
|
62
|
+
protected resolveUrl(url: string): string;
|
|
63
|
+
/** Block execution while paused, polling for resume/step/config commands. */
|
|
64
|
+
private checkPauseState;
|
|
65
|
+
private emitJSON;
|
|
66
|
+
/** Handle an IPC command from the server (via stdin or signal). */
|
|
67
|
+
handleCommand(cmd: IPCCommand): void;
|
|
68
|
+
}
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=base-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-adapter.d.ts","sourceRoot":"","sources":["../../../src/adapters/base-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAErF,UAAU,UAAU;IAClB,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;IAChD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAQD,8BAAsB,WAAY,YAAW,cAAc;IACzD,SAAS,CAAC,MAAM,CAAC,WAAW,SAA6B;IAEzD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAS;;IAwBxB,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/C,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC7D,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3F,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACnD,QAAQ,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACzD,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAClF,QAAQ,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACzD,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IACtD,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IACvD,QAAQ,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IACzF,QAAQ,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IACvF,QAAQ,CAAC,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAClD,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACzC,QAAQ,CAAC,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACnE,QAAQ,CAAC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IACtE,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACrD,QAAQ,CAAC,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACtE,QAAQ,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACtE,QAAQ,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACtE,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACrF,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IACpE,QAAQ,CAAC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAExC,uFAAuF;cACvE,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBrE,wCAAwC;IACxC,SAAS,CAAC,iBAAiB,IAAI,IAAI;IAInC,+CAA+C;cAC/B,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYjD,gEAAgE;IAChE,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAOzC,6EAA6E;YAC/D,eAAe;IAM7B,OAAO,CAAC,QAAQ;IAIhB,mEAAmE;IACnE,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;CAsBrC"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* BaseAdapter — abstract class providing debugger infrastructure.
|
|
4
|
+
* Breakpoints, pause/resume, step mode, slow mode, IPC via signals + stdin.
|
|
5
|
+
*
|
|
6
|
+
* All mutable debugger state is instance-scoped (not static), making
|
|
7
|
+
* the adapter testable and safe for potential multi-instance use.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.BaseAdapter = void 0;
|
|
11
|
+
/** Whether IPC (signals + stdin) has been initialized for this process. */
|
|
12
|
+
let ipcInitialized = false;
|
|
13
|
+
/** The single adapter instance in this process that receives IPC commands. */
|
|
14
|
+
let activeAdapter = null;
|
|
15
|
+
class BaseAdapter {
|
|
16
|
+
static downloadDir = '/tmp/glassbox-downloads';
|
|
17
|
+
isPaused = false;
|
|
18
|
+
stepMode = false;
|
|
19
|
+
stepsRemaining = 0;
|
|
20
|
+
actionIndex = 0;
|
|
21
|
+
breakpoints;
|
|
22
|
+
actionDelay;
|
|
23
|
+
baseUrl;
|
|
24
|
+
constructor() {
|
|
25
|
+
this.baseUrl = process.env.TEST_DOMAIN ?? '';
|
|
26
|
+
this.actionDelay = parseInt(process.env.ACTION_DELAY ?? '0', 10);
|
|
27
|
+
const breakpointsEnv = process.env.TEST_BREAKPOINTS;
|
|
28
|
+
this.breakpoints = breakpointsEnv
|
|
29
|
+
? new Set(JSON.parse(breakpointsEnv))
|
|
30
|
+
: new Set();
|
|
31
|
+
// Register this as the active adapter for IPC routing
|
|
32
|
+
if (activeAdapter && activeAdapter !== this) {
|
|
33
|
+
process.stderr.write('[WARN] Multiple BaseAdapter instances in same process — IPC will route to the latest only\n');
|
|
34
|
+
}
|
|
35
|
+
activeAdapter = this;
|
|
36
|
+
if (!ipcInitialized) {
|
|
37
|
+
initializeIPC();
|
|
38
|
+
ipcInitialized = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Called BEFORE each adapter action — handles breakpoints, pause state, step mode. */
|
|
42
|
+
async logAndCheckAction(description) {
|
|
43
|
+
this.actionIndex++;
|
|
44
|
+
const index = this.actionIndex;
|
|
45
|
+
this.emitJSON({ type: 'action', index, status: 'start', description });
|
|
46
|
+
if (this.breakpoints.has(index)) {
|
|
47
|
+
this.emitJSON({ type: 'action', index, status: 'breakpoint', description });
|
|
48
|
+
this.isPaused = true;
|
|
49
|
+
}
|
|
50
|
+
await this.checkPauseState();
|
|
51
|
+
if (this.stepMode) {
|
|
52
|
+
this.stepsRemaining--;
|
|
53
|
+
if (this.stepsRemaining <= 0) {
|
|
54
|
+
this.isPaused = true;
|
|
55
|
+
this.stepMode = false;
|
|
56
|
+
this.emitJSON({ type: 'ui-control', action: 'pause' });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Called AFTER each adapter action. */
|
|
61
|
+
logActionComplete() {
|
|
62
|
+
this.emitJSON({ type: 'action', index: this.actionIndex, status: 'complete' });
|
|
63
|
+
}
|
|
64
|
+
/** Applies slow-mode delay between actions. */
|
|
65
|
+
async addSlowModeDelay() {
|
|
66
|
+
if (this.actionDelay <= 0)
|
|
67
|
+
return;
|
|
68
|
+
const interval = 50;
|
|
69
|
+
let remaining = this.actionDelay;
|
|
70
|
+
while (remaining > 0) {
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(interval, remaining)));
|
|
72
|
+
remaining -= interval;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Resolve relative URLs against the base URL (TEST_DOMAIN). */
|
|
76
|
+
resolveUrl(url) {
|
|
77
|
+
if (url.startsWith('http://') || url.startsWith('https://'))
|
|
78
|
+
return url;
|
|
79
|
+
const base = this.baseUrl.replace(/\/$/, '');
|
|
80
|
+
const path = url.startsWith('/') ? url : `/${url}`;
|
|
81
|
+
return `${base}${path}`;
|
|
82
|
+
}
|
|
83
|
+
/** Block execution while paused, polling for resume/step/config commands. */
|
|
84
|
+
async checkPauseState() {
|
|
85
|
+
while (this.isPaused) {
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
emitJSON(data) {
|
|
90
|
+
process.stderr.write(`[JSON]${JSON.stringify(data)}\n`);
|
|
91
|
+
}
|
|
92
|
+
/** Handle an IPC command from the server (via stdin or signal). */
|
|
93
|
+
handleCommand(cmd) {
|
|
94
|
+
switch (cmd.command) {
|
|
95
|
+
case 'pause':
|
|
96
|
+
this.isPaused = true;
|
|
97
|
+
this.emitJSON({ type: 'ui-control', action: 'pause' });
|
|
98
|
+
break;
|
|
99
|
+
case 'resume':
|
|
100
|
+
this.isPaused = false;
|
|
101
|
+
this.stepMode = false;
|
|
102
|
+
this.emitJSON({ type: 'ui-control', action: 'resume' });
|
|
103
|
+
break;
|
|
104
|
+
case 'step':
|
|
105
|
+
this.isPaused = false;
|
|
106
|
+
this.stepMode = true;
|
|
107
|
+
this.stepsRemaining = cmd.stepsRemaining ?? 1;
|
|
108
|
+
break;
|
|
109
|
+
case 'config':
|
|
110
|
+
if (cmd.actionDelay !== undefined)
|
|
111
|
+
this.actionDelay = cmd.actionDelay;
|
|
112
|
+
if (cmd.breakpoints)
|
|
113
|
+
this.breakpoints = new Set(cmd.breakpoints);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.BaseAdapter = BaseAdapter;
|
|
119
|
+
/**
|
|
120
|
+
* Initialize process-level IPC (signals + stdin).
|
|
121
|
+
* Routes commands to whichever adapter instance is currently active.
|
|
122
|
+
* Called once per process lifetime.
|
|
123
|
+
*/
|
|
124
|
+
function initializeIPC() {
|
|
125
|
+
try {
|
|
126
|
+
process.on('SIGUSR1', () => {
|
|
127
|
+
activeAdapter?.handleCommand({ command: 'pause' });
|
|
128
|
+
});
|
|
129
|
+
process.on('SIGUSR2', () => {
|
|
130
|
+
activeAdapter?.handleCommand({ command: 'resume' });
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Signals may not be available on all platforms
|
|
135
|
+
}
|
|
136
|
+
let stdinBuffer = '';
|
|
137
|
+
process.stdin.setEncoding('utf-8');
|
|
138
|
+
process.stdin.on('data', (chunk) => {
|
|
139
|
+
stdinBuffer += chunk;
|
|
140
|
+
const lines = stdinBuffer.split('\n');
|
|
141
|
+
stdinBuffer = lines.pop() ?? '';
|
|
142
|
+
for (const line of lines) {
|
|
143
|
+
if (!line.trim())
|
|
144
|
+
continue;
|
|
145
|
+
try {
|
|
146
|
+
const cmd = JSON.parse(line);
|
|
147
|
+
activeAdapter?.handleCommand(cmd);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// Ignore malformed JSON
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=base-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-adapter.js","sourceRoot":"","sources":["../../../src/adapters/base-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrowserAdapter interface — the contract that page objects consume.
|
|
3
|
+
* The CDPAdapter is the sole implementation.
|
|
4
|
+
*/
|
|
5
|
+
export interface DownloadResult {
|
|
6
|
+
filePath: string;
|
|
7
|
+
suggestedFilename: string;
|
|
8
|
+
}
|
|
9
|
+
export interface BrowserAdapter {
|
|
10
|
+
click(selector: string): Promise<void>;
|
|
11
|
+
fill(selector: string, value: string): Promise<void>;
|
|
12
|
+
type(selector: string, value: string, options?: {
|
|
13
|
+
delay?: number;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
getText(selector: string): Promise<string>;
|
|
16
|
+
getInputValue(selector: string): Promise<string>;
|
|
17
|
+
getAttribute(selector: string, attribute: string): Promise<string | null>;
|
|
18
|
+
countElements(selector: string): Promise<number>;
|
|
19
|
+
isVisible(selector: string): Promise<boolean>;
|
|
20
|
+
isDisabled(selector: string): Promise<boolean>;
|
|
21
|
+
waitForSelector(selector: string, options?: {
|
|
22
|
+
timeout?: number;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
waitForHidden(selector: string, options?: {
|
|
25
|
+
timeout?: number;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
waitForTimeout(ms: number): Promise<void>;
|
|
28
|
+
goto(url: string): Promise<void>;
|
|
29
|
+
clickAndWaitForNavigation(selector: string): Promise<void>;
|
|
30
|
+
getCurrentUrl(): Promise<string>;
|
|
31
|
+
evaluate<T = unknown>(script: string | (() => T)): Promise<T>;
|
|
32
|
+
clearSession(origin?: string): Promise<void>;
|
|
33
|
+
readClipboard(): Promise<string>;
|
|
34
|
+
uploadFile(selector: string, filePath: string): Promise<void>;
|
|
35
|
+
selectByIndex(selector: string, index: number): Promise<void>;
|
|
36
|
+
selectByValue(selector: string, value: string): Promise<void>;
|
|
37
|
+
selectByText(selector: string, text: string, exact?: boolean): Promise<void>;
|
|
38
|
+
clickAndDownload(selector: string): Promise<DownloadResult>;
|
|
39
|
+
clearDownloads(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=browser-adapter.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-adapter.interface.d.ts","sourceRoot":"","sources":["../../../src/adapters/browser-adapter.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,cAAc;IAE7B,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAGnF,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3C,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC1E,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjD,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAG/C,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjF,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG1C,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAGjC,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAG9D,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IACjC,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG9D,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG7E,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAC5D,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC"}
|