@matware/e2e-runner 1.2.1 → 1.3.1
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/.claude-plugin/marketplace.json +52 -0
- package/.claude-plugin/plugin.json +17 -3
- package/.mcp.json +2 -2
- package/.opencode/commands/create-test.md +63 -0
- package/.opencode/commands/run.md +50 -0
- package/.opencode/commands/verify-issue.md +62 -0
- package/.opencode/skills/e2e-testing/SKILL.md +181 -0
- package/.opencode/skills/e2e-testing/references/action-types.md +143 -0
- package/.opencode/skills/e2e-testing/references/auth-strategies.md +91 -0
- package/.opencode/skills/e2e-testing/references/graphql.md +59 -0
- package/.opencode/skills/e2e-testing/references/issue-verification.md +59 -0
- package/.opencode/skills/e2e-testing/references/multi-pool.md +60 -0
- package/.opencode/skills/e2e-testing/references/network-debugging.md +62 -0
- package/.opencode/skills/e2e-testing/references/test-json-format.md +163 -0
- package/.opencode/skills/e2e-testing/references/troubleshooting.md +224 -0
- package/.opencode/skills/e2e-testing/references/variables.md +41 -0
- package/.opencode/skills/e2e-testing/references/visual-verification.md +89 -0
- package/LICENSE +190 -0
- package/OPENCODE.md +166 -0
- package/README.md +165 -104
- package/agents/test-creator.md +54 -1
- package/agents/test-improver.md +37 -0
- package/bin/cli.js +409 -16
- package/commands/capture.md +45 -0
- package/commands/create-test.md +16 -1
- package/opencode.json +11 -0
- package/package.json +7 -2
- package/scripts/setup-opencode.sh +113 -0
- package/skills/e2e-testing/SKILL.md +10 -3
- package/skills/e2e-testing/references/action-types.md +48 -5
- package/skills/e2e-testing/references/auth-strategies.md +91 -0
- package/skills/e2e-testing/references/graphql.md +59 -0
- package/skills/e2e-testing/references/issue-verification.md +59 -0
- package/skills/e2e-testing/references/multi-pool.md +60 -0
- package/skills/e2e-testing/references/network-debugging.md +62 -0
- package/skills/e2e-testing/references/test-json-format.md +4 -0
- package/skills/e2e-testing/references/troubleshooting.md +44 -2
- package/skills/e2e-testing/references/variables.md +41 -0
- package/skills/e2e-testing/references/visual-verification.md +89 -0
- package/src/actions.js +475 -2
- package/src/ai-generate.js +139 -8
- package/src/app-pool.js +339 -0
- package/src/config.js +266 -5
- package/src/dashboard.js +216 -17
- package/src/db.js +191 -7
- package/src/index.js +12 -9
- package/src/learner-sqlite.js +458 -0
- package/src/learner.js +78 -6
- package/src/mcp-tools.js +1348 -51
- package/src/module-resolver.js +37 -0
- package/src/narrate.js +65 -0
- package/src/pool-manager.js +229 -0
- package/src/pool.js +301 -31
- package/src/reporter.js +86 -2
- package/src/runner.js +480 -71
- package/src/sync/auth.js +354 -0
- package/src/sync/client.js +572 -0
- package/src/sync/hub-routes.js +816 -0
- package/src/sync/index.js +68 -0
- package/src/sync/middleware.js +347 -0
- package/src/sync/queue.js +209 -0
- package/src/sync/schema.js +540 -0
- package/src/verify.js +10 -7
- package/src/visual-diff.js +446 -0
- package/src/watch.js +384 -0
- package/templates/build-dashboard.js +47 -6
- package/templates/dashboard/js/api.js +62 -0
- package/templates/dashboard/js/init.js +13 -0
- package/templates/dashboard/js/keyboard.js +46 -0
- package/templates/dashboard/js/state.js +40 -0
- package/templates/dashboard/js/toast.js +41 -0
- package/templates/dashboard/js/utils.js +216 -0
- package/templates/dashboard/js/view-live.js +181 -0
- package/templates/dashboard/js/view-runs.js +676 -0
- package/templates/dashboard/js/view-tests.js +294 -0
- package/templates/dashboard/js/view-watch.js +242 -0
- package/templates/dashboard/js/websocket.js +116 -0
- package/templates/dashboard/styles/base.css +69 -0
- package/templates/dashboard/styles/components.css +117 -0
- package/templates/dashboard/styles/view-live.css +97 -0
- package/templates/dashboard/styles/view-runs.css +243 -0
- package/templates/dashboard/styles/view-tests.css +96 -0
- package/templates/dashboard/styles/view-watch.css +53 -0
- package/templates/dashboard/template.html +181 -100
- package/templates/dashboard.html +1614 -547
- package/templates/sample-test.json +0 -8
- package/templates/dashboard/app.js +0 -1152
- package/templates/dashboard/styles.css +0 -413
package/src/learner.js
CHANGED
|
@@ -17,8 +17,12 @@ const ERROR_CATEGORIES = [
|
|
|
17
17
|
{ pattern: /waitForSelector/i, category: 'selector-not-found' },
|
|
18
18
|
{ pattern: /not visible/i, category: 'selector-not-found' },
|
|
19
19
|
{ pattern: /navigation/i, category: 'navigation-error' },
|
|
20
|
-
{ pattern: /
|
|
20
|
+
{ pattern: /ERR_NAME_NOT_RESOLVED/i, category: 'dns-resolution' },
|
|
21
21
|
{ pattern: /ERR_CONNECTION_REFUSED/i, category: 'connection-refused' },
|
|
22
|
+
{ pattern: /ECONNREFUSED/i, category: 'connection-refused' },
|
|
23
|
+
{ pattern: /Chrome Pool unavailable/i, category: 'pool-unavailable' },
|
|
24
|
+
{ pattern: /Failed to connect to pool/i, category: 'pool-connect-failed' },
|
|
25
|
+
{ pattern: /net::ERR_/i, category: 'network-error' },
|
|
22
26
|
{ pattern: /assert_text/i, category: 'assert-text-failed' },
|
|
23
27
|
{ pattern: /assert_url/i, category: 'assert-url-failed' },
|
|
24
28
|
{ pattern: /assert_visible/i, category: 'assert-visible-failed' },
|
|
@@ -35,6 +39,18 @@ const ERROR_CATEGORIES = [
|
|
|
35
39
|
{ pattern: /evaluate.*ERROR/i, category: 'evaluate-error' },
|
|
36
40
|
];
|
|
37
41
|
|
|
42
|
+
/** Categories that indicate infrastructure failures — not test/app issues. */
|
|
43
|
+
export const INFRA_CATEGORIES = new Set([
|
|
44
|
+
'connection-refused', 'dns-resolution', 'pool-unavailable', 'pool-connect-failed', 'network-error',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
/** Returns true if the error is an infrastructure issue (pool down, DNS, connection refused). */
|
|
48
|
+
export function isInfraError(errorMsg) {
|
|
49
|
+
if (!errorMsg) return false;
|
|
50
|
+
const { category } = categorizeError(errorMsg);
|
|
51
|
+
return INFRA_CATEGORIES.has(category);
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
export function categorizeError(errorMsg) {
|
|
39
55
|
if (!errorMsg) return { category: 'unknown', pattern: 'unknown' };
|
|
40
56
|
|
|
@@ -204,6 +220,11 @@ export function learnFromRun(projectId, runDbId, report, config, suiteName) {
|
|
|
204
220
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
205
221
|
`);
|
|
206
222
|
|
|
223
|
+
const insertActionHealth = d.prepare(`
|
|
224
|
+
INSERT INTO action_health (project_id, run_id, test_name, action_index, action_type, selector, success, duration_ms, console_errors_after, network_errors_after, page_url)
|
|
225
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
226
|
+
`);
|
|
227
|
+
|
|
207
228
|
const upsertErrorPattern = d.prepare(`
|
|
208
229
|
INSERT INTO error_patterns (project_id, pattern, category, occurrence_count, first_seen, last_seen, example_error, example_test)
|
|
209
230
|
VALUES (?, ?, ?, 1, datetime('now'), datetime('now'), ?, ?)
|
|
@@ -214,23 +235,40 @@ export function learnFromRun(projectId, runDbId, report, config, suiteName) {
|
|
|
214
235
|
example_test = excluded.example_test
|
|
215
236
|
`);
|
|
216
237
|
|
|
238
|
+
let infraCount = 0;
|
|
239
|
+
|
|
217
240
|
const tx = d.transaction(() => {
|
|
218
241
|
for (const result of results) {
|
|
219
242
|
const durationMs = (result.endTime && result.startTime)
|
|
220
243
|
? new Date(result.endTime) - new Date(result.startTime)
|
|
221
244
|
: null;
|
|
222
|
-
const isFlaky = result.success && (result.attempt || 1) > 1 ? 1 : 0;
|
|
245
|
+
const isFlaky = result.flaky ? 1 : (result.success && (result.attempt || 1) > 1 ? 1 : 0);
|
|
223
246
|
|
|
224
247
|
// Categorize error
|
|
225
248
|
let errorPattern = null;
|
|
249
|
+
let infraFailure = false;
|
|
226
250
|
if (result.error) {
|
|
227
251
|
const { category, pattern } = categorizeError(result.error);
|
|
228
252
|
errorPattern = category;
|
|
253
|
+
infraFailure = INFRA_CATEGORIES.has(category);
|
|
229
254
|
|
|
230
|
-
//
|
|
255
|
+
// Always track error patterns (even infra) for awareness
|
|
231
256
|
upsertErrorPattern.run(projectId, pattern, category, result.error, result.name);
|
|
232
257
|
}
|
|
233
258
|
|
|
259
|
+
if (infraFailure) {
|
|
260
|
+
infraCount++;
|
|
261
|
+
// Still write test_learnings so run counts are accurate,
|
|
262
|
+
// but skip selector/page/api learnings to avoid polluting metrics
|
|
263
|
+
insertTestLearning.run(
|
|
264
|
+
projectId, runDbId, result.name,
|
|
265
|
+
result.success ? 1 : 0, durationMs, isFlaky,
|
|
266
|
+
result.attempt || 1, result.maxAttempts || 1,
|
|
267
|
+
errorPattern
|
|
268
|
+
);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
234
272
|
// Test-level learning
|
|
235
273
|
insertTestLearning.run(
|
|
236
274
|
projectId, runDbId, result.name,
|
|
@@ -275,6 +313,33 @@ export function learnFromRun(projectId, runDbId, report, config, suiteName) {
|
|
|
275
313
|
api.isError, result.name
|
|
276
314
|
);
|
|
277
315
|
}
|
|
316
|
+
|
|
317
|
+
// Action health — per-action metrics with collateral error estimation
|
|
318
|
+
if (result.actions?.length) {
|
|
319
|
+
const totalConsoleErrors = (result.consoleLogs || []).filter(l => l.type === 'error').length;
|
|
320
|
+
const totalNetworkErrors = (result.networkErrors || []).length;
|
|
321
|
+
const actionCount = result.actions.length;
|
|
322
|
+
let currentPage = '/';
|
|
323
|
+
|
|
324
|
+
for (let i = 0; i < actionCount; i++) {
|
|
325
|
+
const action = result.actions[i];
|
|
326
|
+
if (action.type === 'goto' || action.type === 'navigate') {
|
|
327
|
+
try { currentPage = new URL(action.value, 'http://placeholder').pathname; } catch { currentPage = action.value || '/'; }
|
|
328
|
+
}
|
|
329
|
+
// Estimate collateral errors: later actions inherit more errors (weighted distribution)
|
|
330
|
+
const weight = (i + 1) / actionCount;
|
|
331
|
+
const consoleAfter = action.success === false ? Math.round(totalConsoleErrors * weight) : 0;
|
|
332
|
+
const networkAfter = action.success === false ? Math.round(totalNetworkErrors * weight) : 0;
|
|
333
|
+
|
|
334
|
+
insertActionHealth.run(
|
|
335
|
+
projectId, runDbId, result.name, i,
|
|
336
|
+
action.type || 'unknown', action.selector || null,
|
|
337
|
+
action.success === false ? 0 : 1,
|
|
338
|
+
action.duration || null,
|
|
339
|
+
consoleAfter, networkAfter, currentPage
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
278
343
|
}
|
|
279
344
|
});
|
|
280
345
|
|
|
@@ -287,6 +352,8 @@ export function learnFromRun(projectId, runDbId, report, config, suiteName) {
|
|
|
287
352
|
if (config?.learningsNeo4j) {
|
|
288
353
|
writeToGraph(projectId, runDbId, report, config, suiteName).catch(() => {});
|
|
289
354
|
}
|
|
355
|
+
|
|
356
|
+
return { infraCount };
|
|
290
357
|
}
|
|
291
358
|
|
|
292
359
|
// ── Summary cache ─────────────────────────────────────────────────────────────
|
|
@@ -335,8 +402,11 @@ function updateLearningSummary(projectId, config) {
|
|
|
335
402
|
// Unstable selectors
|
|
336
403
|
const unstableSelectors = d.prepare(`
|
|
337
404
|
SELECT selector,
|
|
405
|
+
MAX(action_type) AS action_type,
|
|
338
406
|
ROUND(AVG(CASE WHEN success = 0 THEN 100.0 ELSE 0.0 END), 1) AS fail_rate,
|
|
339
|
-
COUNT(*) AS total_uses
|
|
407
|
+
COUNT(*) AS total_uses,
|
|
408
|
+
COUNT(DISTINCT test_name) AS used_by_tests,
|
|
409
|
+
MAX(page_url) AS page_url
|
|
340
410
|
FROM selector_learnings
|
|
341
411
|
WHERE project_id = ? AND created_at >= ${cutoff}
|
|
342
412
|
GROUP BY selector
|
|
@@ -349,6 +419,7 @@ function updateLearningSummary(projectId, config) {
|
|
|
349
419
|
const failingPages = d.prepare(`
|
|
350
420
|
SELECT url_path,
|
|
351
421
|
ROUND(AVG(CASE WHEN success = 0 THEN 100.0 ELSE 0.0 END), 1) AS fail_rate,
|
|
422
|
+
COUNT(*) AS total_visits,
|
|
352
423
|
SUM(console_errors) AS console_errors,
|
|
353
424
|
SUM(network_errors) AS network_errors
|
|
354
425
|
FROM page_learnings
|
|
@@ -364,7 +435,8 @@ function updateLearningSummary(projectId, config) {
|
|
|
364
435
|
SELECT endpoint,
|
|
365
436
|
ROUND(AVG(CASE WHEN is_error = 1 THEN 100.0 ELSE 0.0 END), 1) AS error_rate,
|
|
366
437
|
ROUND(AVG(duration_ms)) AS avg_duration_ms,
|
|
367
|
-
COUNT(*) AS total_calls
|
|
438
|
+
COUNT(*) AS total_calls,
|
|
439
|
+
GROUP_CONCAT(DISTINCT status) AS status_codes
|
|
368
440
|
FROM api_learnings
|
|
369
441
|
WHERE project_id = ? AND created_at >= ${cutoff}
|
|
370
442
|
GROUP BY endpoint
|
|
@@ -375,7 +447,7 @@ function updateLearningSummary(projectId, config) {
|
|
|
375
447
|
|
|
376
448
|
// Top errors
|
|
377
449
|
const topErrors = d.prepare(`
|
|
378
|
-
SELECT pattern, category, occurrence_count, last_seen, example_error
|
|
450
|
+
SELECT pattern, category, occurrence_count, first_seen, last_seen, example_error AS example_test
|
|
379
451
|
FROM error_patterns
|
|
380
452
|
WHERE project_id = ?
|
|
381
453
|
ORDER BY occurrence_count DESC
|