@_deep4wee/agent-lens 1.1.0 → 1.2.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 +331 -227
- package/dist/cli.js +1068 -911
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +1277 -1033
- package/dist/cli.mjs.map +1 -1
- package/dist/dsl-BIjVN1M0.d.mts +204 -0
- package/dist/dsl-BIjVN1M0.d.ts +204 -0
- package/dist/index.d.mts +131 -178
- package/dist/index.d.ts +131 -178
- package/dist/index.js +1506 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1479 -3
- package/dist/index.mjs.map +1 -1
- package/dist/plugins/a11y-tree/index.d.mts +11 -0
- package/dist/plugins/a11y-tree/index.d.ts +11 -0
- package/dist/plugins/a11y-tree/index.js +190 -0
- package/dist/plugins/a11y-tree/index.js.map +1 -0
- package/dist/plugins/a11y-tree/index.mjs +155 -0
- package/dist/plugins/a11y-tree/index.mjs.map +1 -0
- package/dist/plugins/desktop-webview2/index.d.mts +14 -0
- package/dist/plugins/desktop-webview2/index.d.ts +14 -0
- package/dist/plugins/desktop-webview2/index.js +258 -0
- package/dist/plugins/desktop-webview2/index.js.map +1 -0
- package/dist/plugins/desktop-webview2/index.mjs +221 -0
- package/dist/plugins/desktop-webview2/index.mjs.map +1 -0
- package/dist/plugins/live-controller/index.d.mts +35 -0
- package/dist/plugins/live-controller/index.d.ts +35 -0
- package/dist/plugins/live-controller/index.js +303 -0
- package/dist/plugins/live-controller/index.js.map +1 -0
- package/dist/plugins/live-controller/index.mjs +261 -0
- package/dist/plugins/live-controller/index.mjs.map +1 -0
- package/dist/plugins/mock-ipc/index.d.mts +30 -0
- package/dist/plugins/mock-ipc/index.d.ts +30 -0
- package/dist/plugins/mock-ipc/index.js +210 -0
- package/dist/plugins/mock-ipc/index.js.map +1 -0
- package/dist/plugins/mock-ipc/index.mjs +181 -0
- package/dist/plugins/mock-ipc/index.mjs.map +1 -0
- package/dist/plugins/visual-diff/index.d.mts +30 -0
- package/dist/plugins/visual-diff/index.d.ts +30 -0
- package/dist/plugins/visual-diff/index.js +163 -0
- package/dist/plugins/visual-diff/index.js.map +1 -0
- package/dist/plugins/visual-diff/index.mjs +127 -0
- package/dist/plugins/visual-diff/index.mjs.map +1 -0
- package/docs/plugins.md +415 -0
- package/package.json +40 -2
- package/skills/agent-lens/SKILL.md +130 -43
- package/skills/agent-lens/examples/07-live-controller-interactive-loop.md +108 -0
- package/skills/agent-lens/examples/08-accessibility-semantic-inspection.md +80 -0
- package/skills/agent-lens/examples/09-visual-regression-and-pixel-diffing.md +66 -0
- package/skills/agent-lens/examples/10-authoring-custom-agent-plugins.md +85 -0
- package/skills/agent-lens/references/plugin-development.md +165 -0
package/docs/plugins.md
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# 🔌 AgentLens Plugin Development Guide
|
|
2
|
+
|
|
3
|
+
AgentLens is built around a lightweight **Microkernel architecture**. The core runner handles process lifecycles, navigation, snapshot orchestration, and reporting. Specialized drivers, mock bridges, and developer tools are implemented as modular, composable **plugins**.
|
|
4
|
+
|
|
5
|
+
Whether you are a developer looking to integrate custom test pipelines or an **AI coding agent** that needs to solve a project-specific constraint on-the-fly, the AgentLens Plugin API provides complete extensibility.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📑 Table of Contents
|
|
10
|
+
|
|
11
|
+
1. [Architecture Overview](#-architecture-overview)
|
|
12
|
+
2. [The `AgentLensPlugin` Interface](#-the-agentlensplugin-interface)
|
|
13
|
+
3. [Lifecycle Hooks Explained](#-lifecycle-hooks-explained)
|
|
14
|
+
4. [Extending `TestContext` (`ctx`)](#-extending-testcontext-ctx)
|
|
15
|
+
5. [Custom Markdown Report Sections](#-custom-markdown-report-sections)
|
|
16
|
+
6. [Plugin Resolution & Loading Priority](#-plugin-resolution--loading-priority)
|
|
17
|
+
7. [Built-in Official Plugins](#-built-in-official-plugins)
|
|
18
|
+
8. [Writing a 1-File Plugin (Agent Guide)](#-writing-a-1-file-plugin-agent-guide)
|
|
19
|
+
9. [Concrete Examples](#-concrete-examples)
|
|
20
|
+
- [Example 1: Custom Auth & LocalStorage Seeder](#example-1-custom-auth--localstorage-seeder)
|
|
21
|
+
- [Example 2: Custom WebGL/Canvas Health Assertion](#example-2-custom-webglcanvas-health-assertion)
|
|
22
|
+
- [Example 3: Performance & Core Web Vitals Monitor](#example-3-performance--core-web-vitals-monitor)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🏗️ Architecture Overview
|
|
27
|
+
|
|
28
|
+
The AgentLens runtime is structured into distinct layers:
|
|
29
|
+
|
|
30
|
+
```mermaid
|
|
31
|
+
flowchart TD
|
|
32
|
+
CLI["CLI / Quick Snap / Live Controller"] --> Runner["Microkernel Runner"]
|
|
33
|
+
Runner --> PM["PluginManager (JITI / ESM loader)"]
|
|
34
|
+
PM --> Builtin["Official Plugins (live, a11y, diff, desktop, ipc)"]
|
|
35
|
+
PM --> Local["Local Workspace Plugins (.agent-lens/plugins/*.ts)"]
|
|
36
|
+
PM --> NPM["Third-Party NPM Plugins"]
|
|
37
|
+
|
|
38
|
+
Runner --> Hooks["Plugin Lifecycle Hooks"]
|
|
39
|
+
Hooks --> Drivers["Custom Drivers (CDP / WebView2 / Remote)"]
|
|
40
|
+
Hooks --> ContextExt["Context Extensions (ctx.customMethod)"]
|
|
41
|
+
Hooks --> ReporterExt["Reporter Sections (Custom Markdown in report.md)"]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Because AgentLens uses `jiti` internally, plugins can be written directly in **TypeScript (`.ts`) or JavaScript (`.js`) without needing any pre-compilation step!**
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 📐 The `AgentLensPlugin` Interface
|
|
49
|
+
|
|
50
|
+
Every plugin is an object implementing the `AgentLensPlugin` interface:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import type { Page, BrowserContext, Browser } from 'playwright';
|
|
54
|
+
import type { TestContext, VisualScenario, ReportData } from 'agent-lens';
|
|
55
|
+
|
|
56
|
+
export interface DriverLaunchResult {
|
|
57
|
+
page: Page;
|
|
58
|
+
context: BrowserContext;
|
|
59
|
+
browser?: Browser;
|
|
60
|
+
stop?: () => Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface PluginHookContext {
|
|
64
|
+
/** The currently running scenario configuration */
|
|
65
|
+
scenario?: VisualScenario;
|
|
66
|
+
/** Target execution mode ('preview' | 'desktop' | string) */
|
|
67
|
+
targetMode: 'desktop' | 'preview' | string;
|
|
68
|
+
/** Absolute path to the output artifacts directory for this run */
|
|
69
|
+
artifactsDir: string;
|
|
70
|
+
/** Active CLI arguments and configuration options */
|
|
71
|
+
cliOptions?: Record<string, unknown>;
|
|
72
|
+
/** Shared mutable state map accessible across all lifecycle hooks */
|
|
73
|
+
state: Map<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AgentLensPlugin {
|
|
77
|
+
/** Unique plugin identifier (e.g. 'auth-token', 'a11y-tree') */
|
|
78
|
+
name: string;
|
|
79
|
+
/** Semantic version string */
|
|
80
|
+
version?: string;
|
|
81
|
+
|
|
82
|
+
/** 1. Lifecycle hook: Runs before browsers or servers launch */
|
|
83
|
+
setup?: (context: PluginHookContext) => Promise<void> | void;
|
|
84
|
+
|
|
85
|
+
/** 2. Driver hook: Overrides default browser launch with a custom session */
|
|
86
|
+
launchSession?: (
|
|
87
|
+
options: { currentViewport: { width: number; height: number }; headed?: boolean },
|
|
88
|
+
hookContext: PluginHookContext
|
|
89
|
+
) => Promise<DriverLaunchResult | undefined>;
|
|
90
|
+
|
|
91
|
+
/** 3. Lifecycle hook: Triggered when the browser context is initialized */
|
|
92
|
+
onContextCreated?: (context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;
|
|
93
|
+
|
|
94
|
+
/** 4. Lifecycle hook: Triggered when the test page is created */
|
|
95
|
+
onPageCreated?: (page: Page, context: BrowserContext, hookContext: PluginHookContext) => Promise<void> | void;
|
|
96
|
+
|
|
97
|
+
/** 5. Context extension: Injects custom methods/properties into `ctx` */
|
|
98
|
+
extendContext?: (
|
|
99
|
+
ctx: TestContext,
|
|
100
|
+
page: Page,
|
|
101
|
+
hookContext: PluginHookContext
|
|
102
|
+
) => Record<string, any> | Promise<Record<string, any>>;
|
|
103
|
+
|
|
104
|
+
/** 6. Lifecycle hook: Triggered after test execution to enrich report.md */
|
|
105
|
+
onAfterRun?: (reportData: ReportData, hookContext: PluginHookContext) => Promise<void> | void;
|
|
106
|
+
|
|
107
|
+
/** 7. Cleanup hook: Guaranteed to execute in a `finally` block */
|
|
108
|
+
teardown?: (hookContext: PluginHookContext) => Promise<void> | void;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Helper function for type safety:
|
|
113
|
+
```typescript
|
|
114
|
+
import { definePlugin } from 'agent-lens';
|
|
115
|
+
|
|
116
|
+
export default definePlugin({
|
|
117
|
+
name: 'my-plugin',
|
|
118
|
+
// ...
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 🔄 Lifecycle Hooks Explained
|
|
125
|
+
|
|
126
|
+
| Hook | When it runs | Primary Use Cases |
|
|
127
|
+
| :--- | :--- | :--- |
|
|
128
|
+
| `setup` | Before any dev server, browser, or scenario starts | Seeding test databases, creating temporary folders, registering shared state |
|
|
129
|
+
| `launchSession` | When the runner needs a browser/page session | Connecting to external remote CDP ports, launching compiled native `.exe` binaries, attaching to Electron apps |
|
|
130
|
+
| `onContextCreated` | Immediately after `browser.newContext()` is created | Calling `context.addInitScript()`, setting cookies, overriding geolocation, setting HTTP authorization headers |
|
|
131
|
+
| `onPageCreated` | When `context.newPage()` is ready | Setting custom user agents, binding CDP listeners, intercepting console/network streams |
|
|
132
|
+
| `extendContext` | Before scenario `run(ctx)` is invoked | Adding custom helper functions to `ctx` (e.g. `ctx.seedUser()`, `ctx.assertCanvas()`) |
|
|
133
|
+
| `onAfterRun` | After `run(ctx)` completes, before report writing | Calculating diffs, computing metrics, appending markdown sections to `reportData.customSections` |
|
|
134
|
+
| `teardown` | In the runner's `finally` block | Cleaning up background processes, restoring mocked files, removing DB records |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 💉 Extending `TestContext` (`ctx`)
|
|
139
|
+
|
|
140
|
+
The `extendContext` hook allows you to expose domain-specific or framework-specific utilities directly to test authors:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// .agent-lens/plugins/theme-tester.ts
|
|
144
|
+
import { definePlugin } from 'agent-lens';
|
|
145
|
+
|
|
146
|
+
export default definePlugin({
|
|
147
|
+
name: 'theme-tester',
|
|
148
|
+
extendContext: (_ctx, page) => {
|
|
149
|
+
return {
|
|
150
|
+
switchTheme: async (theme: 'dark' | 'light' | 'cyberpunk') => {
|
|
151
|
+
await page.evaluate((t) => {
|
|
152
|
+
document.documentElement.setAttribute('data-theme', t);
|
|
153
|
+
}, theme);
|
|
154
|
+
await page.waitForTimeout(150);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
In your scenario:
|
|
162
|
+
```typescript
|
|
163
|
+
import { defineVisualTest } from 'agent-lens';
|
|
164
|
+
|
|
165
|
+
export default defineVisualTest({
|
|
166
|
+
id: 'theme-check',
|
|
167
|
+
run: async (ctx) => {
|
|
168
|
+
// Custom method injected by plugin!
|
|
169
|
+
await (ctx as any).switchTheme('dark');
|
|
170
|
+
await ctx.capture('01_dark_theme');
|
|
171
|
+
|
|
172
|
+
await (ctx as any).switchTheme('cyberpunk');
|
|
173
|
+
await ctx.capture('02_cyberpunk_theme');
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 📊 Custom Markdown Report Sections
|
|
181
|
+
|
|
182
|
+
Plugins can append custom sections directly to `reportData.customSections`. These are automatically rendered by the `VisualReporter` into both `artifacts/<timestamp>/report.md` and `artifacts/latest/report.md`.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
onAfterRun: (reportData, hookContext) => {
|
|
186
|
+
if (!reportData.customSections) return;
|
|
187
|
+
|
|
188
|
+
reportData.customSections.push({
|
|
189
|
+
title: '🚀 Performance Metrics',
|
|
190
|
+
content: `| Metric | Value | Status |
|
|
191
|
+
| :--- | :--- | :-: |
|
|
192
|
+
| First Contentful Paint | 142ms | 🟢 Pass |
|
|
193
|
+
| Largest Contentful Paint | 410ms | 🟢 Pass |
|
|
194
|
+
| Cumulative Layout Shift | 0.002 | 🟢 Pass |`
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 🔍 Plugin Resolution & Loading Priority
|
|
202
|
+
|
|
203
|
+
AgentLens discovers and registers plugins in the following order:
|
|
204
|
+
|
|
205
|
+
1. **CLI Flags**: `--plugin=name1,name2` or `--plugins=name1,name2`
|
|
206
|
+
2. **Configuration File**: `plugins: ["name1"]` in `agent-lens.json` or `package.json`
|
|
207
|
+
3. **Scenario Definition**: `plugins: ['name1', inlinePluginObject]` in scenario file
|
|
208
|
+
4. **Local Project Conventions** (searched in order):
|
|
209
|
+
- `.agent-lens/plugins/<name>.ts`
|
|
210
|
+
- `.agent-lens/plugins/<name>.js`
|
|
211
|
+
- `.agent-lens/plugins/<name>/index.ts`
|
|
212
|
+
- `.agent-lens/plugins/<name>/index.js`
|
|
213
|
+
- `plugins/<name>.ts`
|
|
214
|
+
- `plugins/<name>.js`
|
|
215
|
+
- `plugins/<name>/index.ts`
|
|
216
|
+
- `plugins/<name>/index.js`
|
|
217
|
+
5. **Direct Relative/Absolute Paths**: e.g. `--plugin=./scripts/my-plugin.ts`
|
|
218
|
+
6. **Built-in Plugins**: `desktop-webview2`, `mock-ipc`, `live-controller`, `a11y-tree`, `visual-diff`
|
|
219
|
+
7. **NPM Modules**: Resolves installed packages from `node_modules` (e.g. `@agent-lens/plugin-lighthouse`)
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 📦 Built-in Official Plugins
|
|
224
|
+
|
|
225
|
+
AgentLens ships with 5 core plugins out of the box:
|
|
226
|
+
|
|
227
|
+
### 1. `live-controller`
|
|
228
|
+
- **Purpose**: Provides persistent, interactive background browser sessions for rapid iteration without restarts.
|
|
229
|
+
- **Commands**:
|
|
230
|
+
- `npx agent-lens live start --url=http://localhost:5173`
|
|
231
|
+
- `npx agent-lens live click 450 120` (Vision coordinate clicks)
|
|
232
|
+
- `npx agent-lens live click "button.submit"` (CSS selector clicks)
|
|
233
|
+
- `npx agent-lens live type "input[name=email]" "user@example.com"`
|
|
234
|
+
- `npx agent-lens live snap [name] [--full]` (Capture current state or full scrollable page)
|
|
235
|
+
- `npx agent-lens live stop`
|
|
236
|
+
- **Context Extensions**: `ctx.clickCoords(x, y)`, `ctx.dragAndDrop(fromX, fromY, toX, toY)`, `ctx.scrollPercent(pct)`, `ctx.snapLive(options)`.
|
|
237
|
+
|
|
238
|
+
### 2. `a11y-tree`
|
|
239
|
+
- **Purpose**: Extracts a clean, semantic accessibility hierarchy from the live DOM (roles, accessible names, values, focus states).
|
|
240
|
+
- **Benefits**: Allows text-only LLMs (or lightweight models without vision) to "read" the exact UI layout and verify interactive state.
|
|
241
|
+
- **Outputs**: Generates `artifacts/latest/a11y-tree.md` and appends an accessibility section to `report.md`.
|
|
242
|
+
- **Context Extensions**: `await ctx.dumpAccessibilityTree({ selector?: string, compact?: boolean })`.
|
|
243
|
+
|
|
244
|
+
### 3. `visual-diff`
|
|
245
|
+
- **Purpose**: Performs pixel-by-pixel visual regression testing using `pixelmatch` and `pngjs`.
|
|
246
|
+
- **Outputs**: Generates `*_diff.png` highlighting changed pixels in red/yellow, reports exact changed pixel counts and regression percentages, and attaches a comparison table to `report.md`.
|
|
247
|
+
- **Context Extensions**:
|
|
248
|
+
- `await ctx.compareSnapshots(currentPath, baselinePath, options?)`
|
|
249
|
+
- `await ctx.captureAndCompare(name, baselinePath, captureOptions?, diffOptions?)`
|
|
250
|
+
|
|
251
|
+
### 4. `desktop-webview2`
|
|
252
|
+
- **Purpose**: Native Windows executable testing (`.exe` using WebView2, Electron, or Photino).
|
|
253
|
+
- **Features**: Auto-launches target `.exe` with remote debugging flags, connects via CDP (`localhost:9222`), handles graceful process termination via `treeKill`, and captures startup crash logs.
|
|
254
|
+
- **Activation**: Auto-loaded whenever `--mode=desktop` or `--exe=path/to/app.exe` is passed.
|
|
255
|
+
|
|
256
|
+
### 5. `mock-ipc`
|
|
257
|
+
- **Purpose**: Mocks desktop hybrid bridge calls (`window.__mockIpc` and `window.external.sendMessage`).
|
|
258
|
+
- **Features**: Auto-loads base mocks from `scenarios/mocks.ts` and allows runtime overrides via `await ctx.setMockIpc('ACTION', payload)`.
|
|
259
|
+
- **Activation**: Auto-loaded whenever `mocks.ts` exists or a scenario specifies `mockIpc: [...]`.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 🤖 Writing a 1-File Plugin (Agent Guide)
|
|
264
|
+
|
|
265
|
+
### When should an AI Agent create a plugin?
|
|
266
|
+
When an agent faces a project requirement that cannot be solved by standard CLI flags or standard DSL:
|
|
267
|
+
- The app requires a specific auth token in `localStorage` or `sessionStorage` before mounting.
|
|
268
|
+
- The app requires mock WebSockets or Server-Sent Events (SSE).
|
|
269
|
+
- The app requires custom Canvas/WebGL assertions or pixel color checks.
|
|
270
|
+
- The project has a custom database or SQLite file that needs seeding before the dev server checks.
|
|
271
|
+
|
|
272
|
+
### Step-by-Step Agent Recipe:
|
|
273
|
+
1. Create a single file inside the project at `.agent-lens/plugins/<plugin-name>.ts`.
|
|
274
|
+
2. Implement `definePlugin({ name: '<plugin-name>', ... })` and export it as `default`.
|
|
275
|
+
3. If you need helper methods in your scenario, return them from `extendContext`.
|
|
276
|
+
4. Run your verification:
|
|
277
|
+
```bash
|
|
278
|
+
npx agent-lens snap --plugin=<plugin-name> --url=http://localhost:5173
|
|
279
|
+
# OR
|
|
280
|
+
npx agent-lens --scenario=my-test --plugin=<plugin-name>
|
|
281
|
+
```
|
|
282
|
+
5. Inspect `artifacts/latest/report.md` to verify results.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## 💡 Concrete Examples
|
|
287
|
+
|
|
288
|
+
### Example 1: Custom Auth & LocalStorage Seeder
|
|
289
|
+
|
|
290
|
+
Suppose an app immediately redirects unauthenticated users to `/login`. You can create a plugin to inject a mock JWT into `localStorage` before any scripts run:
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
// .agent-lens/plugins/mock-auth.ts
|
|
294
|
+
import { definePlugin } from 'agent-lens';
|
|
295
|
+
|
|
296
|
+
export default definePlugin({
|
|
297
|
+
name: 'mock-auth',
|
|
298
|
+
version: '1.0.0',
|
|
299
|
+
|
|
300
|
+
onContextCreated: async (context) => {
|
|
301
|
+
// Inject mock session into localStorage before React mounts
|
|
302
|
+
await context.addInitScript(() => {
|
|
303
|
+
window.localStorage.setItem('auth_token', 'mock-agent-jwt-token-xyz');
|
|
304
|
+
window.localStorage.setItem('user_profile', JSON.stringify({
|
|
305
|
+
id: 99,
|
|
306
|
+
name: 'Autonomous Agent',
|
|
307
|
+
role: 'SUPERADMIN',
|
|
308
|
+
permissions: ['read', 'write', 'delete']
|
|
309
|
+
}));
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Run:
|
|
316
|
+
```bash
|
|
317
|
+
npx agent-lens snap --plugin=mock-auth --url=http://localhost:5173/dashboard
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### Example 2: Custom WebGL/Canvas Health Assertion
|
|
323
|
+
|
|
324
|
+
Verify that a 3D canvas is actually drawing shapes and not just rendering a blank transparent canvas:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// .agent-lens/plugins/canvas-validator.ts
|
|
328
|
+
import { definePlugin } from 'agent-lens';
|
|
329
|
+
|
|
330
|
+
export default definePlugin({
|
|
331
|
+
name: 'canvas-validator',
|
|
332
|
+
extendContext: (_ctx, page) => {
|
|
333
|
+
return {
|
|
334
|
+
assertCanvasHasPixels: async (selector = 'canvas') => {
|
|
335
|
+
const hasContent = await page.evaluate((sel) => {
|
|
336
|
+
const canvas = document.querySelector(sel) as HTMLCanvasElement;
|
|
337
|
+
if (!canvas) return false;
|
|
338
|
+
const ctx = canvas.getContext('2d') || canvas.getContext('webgl2') || canvas.getContext('webgl');
|
|
339
|
+
if (!ctx) return false;
|
|
340
|
+
|
|
341
|
+
// Quick probe of center pixel
|
|
342
|
+
const probe = document.createElement('canvas');
|
|
343
|
+
probe.width = 10;
|
|
344
|
+
probe.height = 10;
|
|
345
|
+
const pctx = probe.getContext('2d');
|
|
346
|
+
if (!pctx) return false;
|
|
347
|
+
pctx.drawImage(canvas, 0, 0, 10, 10);
|
|
348
|
+
const imgData = pctx.getImageData(0, 0, 10, 10).data;
|
|
349
|
+
// Check if any non-zero alpha pixel exists
|
|
350
|
+
for (let i = 3; i < imgData.length; i += 4) {
|
|
351
|
+
if (imgData[i] > 0) return true;
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
}, selector);
|
|
355
|
+
|
|
356
|
+
if (!hasContent) {
|
|
357
|
+
throw new Error(`Canvas "${selector}" is completely blank or transparent!`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
### Example 3: Performance & Core Web Vitals Monitor
|
|
368
|
+
|
|
369
|
+
Capture page performance metrics and inject an audit card directly into the final `report.md`:
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
// .agent-lens/plugins/web-vitals.ts
|
|
373
|
+
import { definePlugin } from 'agent-lens';
|
|
374
|
+
|
|
375
|
+
const PERF_STATE_KEY = 'perf_timings';
|
|
376
|
+
|
|
377
|
+
export default definePlugin({
|
|
378
|
+
name: 'web-vitals',
|
|
379
|
+
|
|
380
|
+
onPageCreated: (page, _context, hookContext) => {
|
|
381
|
+
page.on('load', async () => {
|
|
382
|
+
const timings = await page.evaluate(() => {
|
|
383
|
+
const perf = window.performance;
|
|
384
|
+
const timing = perf.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
|
|
385
|
+
if (!timing) return null;
|
|
386
|
+
return {
|
|
387
|
+
domInteractive: Math.round(timing.domInteractive),
|
|
388
|
+
domContentLoaded: Math.round(timing.domContentLoadedEventEnd),
|
|
389
|
+
loadEvent: Math.round(timing.loadEventEnd),
|
|
390
|
+
transferSizeKb: Math.round(timing.transferSize / 1024)
|
|
391
|
+
};
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
if (timings) {
|
|
395
|
+
hookContext.state.set(PERF_STATE_KEY, timings);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
onAfterRun: (reportData, hookContext) => {
|
|
401
|
+
const timings = hookContext.state.get(PERF_STATE_KEY) as Record<string, number> | undefined;
|
|
402
|
+
if (!timings || !reportData.customSections) return;
|
|
403
|
+
|
|
404
|
+
reportData.customSections.push({
|
|
405
|
+
title: '⚡ Web Vitals & Load Performance',
|
|
406
|
+
content: `| Metric | Time / Size | Status |
|
|
407
|
+
| :--- | :--- | :-: |
|
|
408
|
+
| DOM Interactive | ${timings.domInteractive} ms | ${timings.domInteractive < 1000 ? '🟢 Fast' : '🟡 Slow'} |
|
|
409
|
+
| DOM Content Loaded | ${timings.domContentLoaded} ms | ${timings.domContentLoaded < 1500 ? '🟢 Fast' : '🟡 Slow'} |
|
|
410
|
+
| Full Load Complete | ${timings.loadEvent} ms | ${timings.loadEvent < 2500 ? '🟢 Fast' : '🟡 Slow'} |
|
|
411
|
+
| Total Transferred | ${timings.transferSizeKb} KB | 📦 Network |`
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
```
|
package/package.json
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@_deep4wee/agent-lens",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Visual self-check UI runner for AI coding agents",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Visual self-check UI runner for AI coding agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./plugins/desktop-webview2": {
|
|
15
|
+
"types": "./dist/plugins/desktop-webview2/index.d.ts",
|
|
16
|
+
"import": "./dist/plugins/desktop-webview2/index.mjs",
|
|
17
|
+
"require": "./dist/plugins/desktop-webview2/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./plugins/mock-ipc": {
|
|
20
|
+
"types": "./dist/plugins/mock-ipc/index.d.ts",
|
|
21
|
+
"import": "./dist/plugins/mock-ipc/index.mjs",
|
|
22
|
+
"require": "./dist/plugins/mock-ipc/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./plugins/live-controller": {
|
|
25
|
+
"types": "./dist/plugins/live-controller/index.d.ts",
|
|
26
|
+
"import": "./dist/plugins/live-controller/index.mjs",
|
|
27
|
+
"require": "./dist/plugins/live-controller/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./plugins/a11y-tree": {
|
|
30
|
+
"types": "./dist/plugins/a11y-tree/index.d.ts",
|
|
31
|
+
"import": "./dist/plugins/a11y-tree/index.mjs",
|
|
32
|
+
"require": "./dist/plugins/a11y-tree/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./plugins/visual-diff": {
|
|
35
|
+
"types": "./dist/plugins/visual-diff/index.d.ts",
|
|
36
|
+
"import": "./dist/plugins/visual-diff/index.mjs",
|
|
37
|
+
"require": "./dist/plugins/visual-diff/index.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
7
40
|
"bin": {
|
|
8
41
|
"agent-lens": "dist/cli.js"
|
|
9
42
|
},
|
|
@@ -14,6 +47,7 @@
|
|
|
14
47
|
"dist",
|
|
15
48
|
"skills",
|
|
16
49
|
"scripts",
|
|
50
|
+
"docs",
|
|
17
51
|
"README.md",
|
|
18
52
|
"LICENSE"
|
|
19
53
|
],
|
|
@@ -28,12 +62,16 @@
|
|
|
28
62
|
},
|
|
29
63
|
"devDependencies": {
|
|
30
64
|
"@types/node": "^20.19.43",
|
|
65
|
+
"@types/pixelmatch": "^5.2.6",
|
|
66
|
+
"@types/pngjs": "^6.0.5",
|
|
31
67
|
"playwright": "^1.63.0",
|
|
32
68
|
"tsup": "^8.0.2",
|
|
33
69
|
"typescript": "^5.0.0"
|
|
34
70
|
},
|
|
35
71
|
"dependencies": {
|
|
36
72
|
"jiti": "^2.7.0",
|
|
73
|
+
"pixelmatch": "^7.2.0",
|
|
74
|
+
"pngjs": "^7.0.0",
|
|
37
75
|
"tree-kill": "^1.2.2"
|
|
38
76
|
},
|
|
39
77
|
"author": "deep4wee",
|