sela-core 1.0.6 → 1.0.7
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 +1 -1
- package/dist/config/ConfigLoader.d.ts +1 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -1
- package/dist/config/ConfigLoader.js +10 -0
- package/dist/config/DryRunGuard.d.ts +14 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -0
- package/dist/config/DryRunGuard.js +79 -0
- package/dist/config/SelaConfig.d.ts +15 -1
- package/dist/config/SelaConfig.d.ts.map +1 -1
- package/dist/config/SelaConfig.js +27 -1
- package/dist/engine/SelaEngine.d.ts +12 -9
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +249 -72
- package/dist/errors/SelaError.d.ts +133 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +155 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -1
- package/dist/fixtures/expectProxy.js +7 -0
- package/dist/fixtures/index.d.ts +7 -1
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +15 -0
- package/dist/fixtures/proxyTag.d.ts +12 -0
- package/dist/fixtures/proxyTag.d.ts.map +1 -0
- package/dist/fixtures/proxyTag.js +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/reporter/SelaReporter.d.ts +110 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +328 -0
- package/dist/services/ASTSourceUpdater.d.ts +19 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +173 -29
- package/dist/services/HealReportService.d.ts +42 -0
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +80 -1
- package/dist/services/HealingCacheService.d.ts +135 -0
- package/dist/services/HealingCacheService.d.ts.map +1 -0
- package/dist/services/HealingCacheService.js +460 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -1
- package/dist/services/InitializerUpdater.js +20 -1
- package/dist/services/IntentAuditor.d.ts +18 -1
- package/dist/services/IntentAuditor.d.ts.map +1 -1
- package/dist/services/IntentAuditor.js +19 -6
- package/dist/services/InteractiveReview.d.ts +24 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +218 -0
- package/dist/services/LLMService.d.ts +17 -1
- package/dist/services/LLMService.d.ts.map +1 -1
- package/dist/services/LLMService.js +18 -8
- package/dist/services/PRAutomationService.d.ts +14 -1
- package/dist/services/PRAutomationService.d.ts.map +1 -1
- package/dist/services/PRAutomationService.js +260 -69
- package/dist/services/PendingPromptLedger.d.ts +44 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -0
- package/dist/services/PendingPromptLedger.js +180 -0
- package/dist/services/ReportGenerator.d.ts +70 -0
- package/dist/services/ReportGenerator.d.ts.map +1 -0
- package/dist/services/ReportGenerator.js +191 -0
- package/dist/services/SafetyGuard.d.ts +27 -1
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SafetyGuard.js +58 -12
- package/dist/services/SourceUpdater.d.ts.map +1 -1
- package/dist/services/SourceUpdater.js +44 -13
- package/dist/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +202 -0
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +379 -0
- package/package.json +71 -67
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/errors/SelaError.ts
|
|
3
|
+
//
|
|
4
|
+
// Structured error hierarchy for Sela.
|
|
5
|
+
//
|
|
6
|
+
// Every error thrown across the heal pipeline MUST be a SelaError so that:
|
|
7
|
+
// 1. Callers can branch on `err.subsystem` and `err.code` without
|
|
8
|
+
// string-parsing `err.message`.
|
|
9
|
+
// 2. Telemetry / report layers get a machine-readable JSON shape.
|
|
10
|
+
// 3. The canonical message format is enforced by the constructor and
|
|
11
|
+
// cannot drift over time: `[Sela-<Subsystem>] <CODE>: <reason>`
|
|
12
|
+
//
|
|
13
|
+
// Public message regex (used by tests/unit/engine/error-indicativity.test.ts):
|
|
14
|
+
// /^\[Sela-(SafetyGuard|IntentAuditor|ASTUpdater|Proxy|LLMService|Registry|ChainValidator|Engine)\] [A-Z][A-Z0-9_]+: .+$/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.CHAIN_VALIDATOR_CODES = exports.REPORT_CODES = exports.REGISTRY_CODES = exports.PROXY_CODES = exports.AST_UPDATER_CODES = exports.LLM_SERVICE_CODES = exports.INTENT_AUDITOR_CODES = exports.SAFETY_GUARD_CODES = exports.SelaError = void 0;
|
|
17
|
+
exports.isSelaError = isSelaError;
|
|
18
|
+
exports.isSelaErrorFrom = isSelaErrorFrom;
|
|
19
|
+
// ───────────────────────────────────────────────────────────────────
|
|
20
|
+
// SelaError — single class for the whole hierarchy.
|
|
21
|
+
//
|
|
22
|
+
// We intentionally avoid a tree of subclasses; `instanceof SelaError` +
|
|
23
|
+
// `err.subsystem === "X"` is more flexible for callers and keeps the
|
|
24
|
+
// surface area tiny.
|
|
25
|
+
// ───────────────────────────────────────────────────────────────────
|
|
26
|
+
const CODE_PATTERN = /^[A-Z][A-Z0-9_]+$/;
|
|
27
|
+
class SelaError extends Error {
|
|
28
|
+
subsystem;
|
|
29
|
+
code;
|
|
30
|
+
reason;
|
|
31
|
+
context;
|
|
32
|
+
constructor(init) {
|
|
33
|
+
if (!CODE_PATTERN.test(init.code)) {
|
|
34
|
+
throw new Error(`[SelaError] invalid code "${init.code}" — must match ${CODE_PATTERN}`);
|
|
35
|
+
}
|
|
36
|
+
const formatted = SelaError.formatMessage(init.subsystem, init.code, init.reason);
|
|
37
|
+
super(formatted);
|
|
38
|
+
this.name = `SelaError(${init.subsystem})`;
|
|
39
|
+
this.subsystem = init.subsystem;
|
|
40
|
+
this.code = init.code;
|
|
41
|
+
this.reason = init.reason;
|
|
42
|
+
this.context = init.context ?? {};
|
|
43
|
+
if (init.cause !== undefined) {
|
|
44
|
+
this.cause = init.cause;
|
|
45
|
+
}
|
|
46
|
+
// Maintain prototype chain through transpilation (ES5 target).
|
|
47
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
48
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
49
|
+
Error
|
|
50
|
+
.captureStackTrace(this, new.target);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
static formatMessage(subsystem, code, reason) {
|
|
54
|
+
return `[Sela-${subsystem}] ${code}: ${reason}`;
|
|
55
|
+
}
|
|
56
|
+
toJSON() {
|
|
57
|
+
return {
|
|
58
|
+
name: this.name,
|
|
59
|
+
subsystem: this.subsystem,
|
|
60
|
+
code: this.code,
|
|
61
|
+
reason: this.reason,
|
|
62
|
+
message: this.message,
|
|
63
|
+
context: this.context,
|
|
64
|
+
stack: this.stack,
|
|
65
|
+
cause: this.cause instanceof Error
|
|
66
|
+
? this.cause.message
|
|
67
|
+
: undefined,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.SelaError = SelaError;
|
|
72
|
+
// ───────────────────────────────────────────────────────────────────
|
|
73
|
+
// Type guards
|
|
74
|
+
// ───────────────────────────────────────────────────────────────────
|
|
75
|
+
function isSelaError(err) {
|
|
76
|
+
return err instanceof SelaError;
|
|
77
|
+
}
|
|
78
|
+
function isSelaErrorFrom(err, subsystem) {
|
|
79
|
+
return err instanceof SelaError && err.subsystem === subsystem;
|
|
80
|
+
}
|
|
81
|
+
// ───────────────────────────────────────────────────────────────────
|
|
82
|
+
// Canonical code catalogue
|
|
83
|
+
//
|
|
84
|
+
// Tests assert against these constants — never hand-write code strings
|
|
85
|
+
// at throw sites. Adding a new code requires adding it here first so
|
|
86
|
+
// the catalogue stays in sync with the error-indicativity contract.
|
|
87
|
+
// ───────────────────────────────────────────────────────────────────
|
|
88
|
+
exports.SAFETY_GUARD_CODES = {
|
|
89
|
+
FAIL_HARD_VISIBILITY_GHOST: "SG_FAIL_HARD_VISIBILITY_GHOST",
|
|
90
|
+
FAIL_HARD_VISIBILITY_OCCLUDED: "SG_FAIL_HARD_VISIBILITY_OCCLUDED",
|
|
91
|
+
BLOCKED_LOW_CONFIDENCE: "SG_BLOCKED_LOW_CONFIDENCE",
|
|
92
|
+
FAIL_HARD_SEMANTIC_INVERSION: "SG_FAIL_HARD_SEMANTIC_INVERSION",
|
|
93
|
+
FAIL_HARD_STATUS_INVERSION: "SG_FAIL_HARD_STATUS_INVERSION",
|
|
94
|
+
BLOCKED_ROLE_SWAP_ASSERTION: "SG_BLOCKED_ROLE_SWAP_ASSERTION",
|
|
95
|
+
FAIL_HARD_AUDITOR_INCONSISTENT: "SG_FAIL_HARD_AUDITOR_INCONSISTENT",
|
|
96
|
+
BLOCKED_AUDITOR_SUSPICIOUS: "SG_BLOCKED_AUDITOR_SUSPICIOUS",
|
|
97
|
+
BLOCKED_SIMILARITY_FLOOR: "SG_BLOCKED_SIMILARITY_FLOOR",
|
|
98
|
+
/** Generic dispatcher when SafetyDecision arrives without an explicit code. */
|
|
99
|
+
GENERIC_REJECTION: "SG_GENERIC_REJECTION",
|
|
100
|
+
};
|
|
101
|
+
exports.INTENT_AUDITOR_CODES = {
|
|
102
|
+
/** Caller asked for an audit but no Anthropic client was configured. */
|
|
103
|
+
DISABLED: "IA_DISABLED",
|
|
104
|
+
INVALID_JSON: "IA_INVALID_JSON",
|
|
105
|
+
CALL_FAILED: "IA_CALL_FAILED",
|
|
106
|
+
};
|
|
107
|
+
exports.LLM_SERVICE_CODES = {
|
|
108
|
+
NO_API_KEY: "LLM_NO_API_KEY",
|
|
109
|
+
INVALID_JSON: "LLM_INVALID_JSON",
|
|
110
|
+
MISSING_SEGMENTS: "LLM_MISSING_SEGMENTS",
|
|
111
|
+
NOT_FOUND: "LLM_NOT_FOUND",
|
|
112
|
+
API_CALL_FAILED: "LLM_API_CALL_FAILED",
|
|
113
|
+
};
|
|
114
|
+
exports.AST_UPDATER_CODES = {
|
|
115
|
+
UNSUPPORTED_NODE: "AST_UNSUPPORTED_NODE",
|
|
116
|
+
TEMPLATE_LITERAL_SKIP: "AST_TEMPLATE_LITERAL_SKIP",
|
|
117
|
+
CIRCULAR_RECEIVER: "AST_CIRCULAR_RECEIVER",
|
|
118
|
+
DEFINITION_NOT_FOUND: "AST_DEFINITION_NOT_FOUND",
|
|
119
|
+
MAX_DEPTH_EXCEEDED: "AST_MAX_DEPTH_EXCEEDED",
|
|
120
|
+
SAVE_FAILED: "AST_SAVE_FAILED",
|
|
121
|
+
/**
|
|
122
|
+
* Developer pinned the failing statement with `// sela-fail-fast`.
|
|
123
|
+
* Heal pipeline is aborted before any LLM call; FailedEvent is
|
|
124
|
+
* recorded with category "Skipped by Developer".
|
|
125
|
+
*/
|
|
126
|
+
HEAL_DISABLED_BY_DIRECTIVE: "AST_HEAL_DISABLED_BY_DIRECTIVE",
|
|
127
|
+
};
|
|
128
|
+
exports.PROXY_CODES = {
|
|
129
|
+
FRAME_SCOPED: "PRX_FRAME_SCOPED",
|
|
130
|
+
REBUILD_FAILED: "PRX_REBUILD_FAILED",
|
|
131
|
+
NON_LOCATOR_TARGET: "PRX_NON_LOCATOR_TARGET",
|
|
132
|
+
};
|
|
133
|
+
exports.REGISTRY_CODES = {
|
|
134
|
+
CONTEXT_MISMATCH: "REG_CONTEXT_MISMATCH",
|
|
135
|
+
FINGERPRINT_COLLISION: "REG_FINGERPRINT_COLLISION",
|
|
136
|
+
};
|
|
137
|
+
exports.REPORT_CODES = {
|
|
138
|
+
/** Event passed to the validator was null, undefined, or not an object. */
|
|
139
|
+
MALFORMED_EVENT: "REP_MALFORMED_EVENT",
|
|
140
|
+
/** Event.kind is not one of HEALED|FAILED|PROTECTED. */
|
|
141
|
+
UNKNOWN_KIND: "REP_UNKNOWN_KIND",
|
|
142
|
+
/** Event is missing a field declared mandatory for its kind. */
|
|
143
|
+
MISSING_FIELD: "REP_MISSING_FIELD",
|
|
144
|
+
/** Event contains a field of the wrong type. */
|
|
145
|
+
INVALID_FIELD_TYPE: "REP_INVALID_FIELD_TYPE",
|
|
146
|
+
/** Disk write failed during flushToDisk(). */
|
|
147
|
+
FLUSH_FAILED: "REP_FLUSH_FAILED",
|
|
148
|
+
};
|
|
149
|
+
exports.CHAIN_VALIDATOR_CODES = {
|
|
150
|
+
TYPE_FLOW: "CV_TYPE_FLOW",
|
|
151
|
+
SCHEMA: "CV_SCHEMA",
|
|
152
|
+
RECEIVER: "CV_RECEIVER",
|
|
153
|
+
PLACEMENT: "CV_PLACEMENT",
|
|
154
|
+
SEMANTIC_DOWNGRADE: "CV_SEMANTIC_DOWNGRADE",
|
|
155
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expectProxy.d.ts","sourceRoot":"","sources":["../../src/fixtures/expectProxy.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,OAAO,EAER,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"expectProxy.d.ts","sourceRoot":"","sources":["../../src/fixtures/expectProxy.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,OAAO,EAER,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAiOlD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,OAAO,kBAAkB,EAAE,IAAI,EACrC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EAAE,6CAA6C;AAClE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,GAChD,CAAC,cAAc,EAAE,OAAO,KAAK,GAAG,CAsElC"}
|
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.createHealingExpect = createHealingExpect;
|
|
5
5
|
const test_1 = require("@playwright/test");
|
|
6
6
|
const HealingRegistry_1 = require("../engine/HealingRegistry");
|
|
7
|
+
const proxyTag_1 = require("./proxyTag");
|
|
7
8
|
const ELEMENT_NOT_FOUND_PATTERNS = [
|
|
8
9
|
/element.*not found/i,
|
|
9
10
|
/locator.*resolved to.*\d+ element/i,
|
|
@@ -225,6 +226,12 @@ resolveProxy) {
|
|
|
225
226
|
const ctx = { filePath, line: actualLine };
|
|
226
227
|
const proxy = new Proxy(base, {
|
|
227
228
|
get(target, prop) {
|
|
229
|
+
// Sela proxy identity — honored before any string-only branch so
|
|
230
|
+
// callers can detect a Sela expect proxy via Symbol.for("sela.proxy").
|
|
231
|
+
if (prop === proxyTag_1.SELA_PROXY)
|
|
232
|
+
return "expect";
|
|
233
|
+
if (typeof prop !== "string")
|
|
234
|
+
return target[prop];
|
|
228
235
|
const val = target[prop];
|
|
229
236
|
if (typeof val === "function" &&
|
|
230
237
|
WRAPPED_MATCHERS.includes(prop)) {
|
package/dist/fixtures/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { Locator } from "@playwright/test";
|
|
1
|
+
import { Locator, Page } from "@playwright/test";
|
|
2
2
|
import { createHealingExpect } from "./expectProxy";
|
|
3
3
|
export declare function resolveFixwrightProxy(value: unknown): Locator | null;
|
|
4
|
+
export declare function createFrameLocatorProxy(rawFrameLocator: any, frameSelector: string, rawPage: Page, testTitle: string, actionCounter: {
|
|
5
|
+
value: number;
|
|
6
|
+
}, parentChain: string): any;
|
|
7
|
+
export declare function createLocatorProxy(rawLocator: Locator, selector: string, rawPage: Page, testTitle: string, actionCounter: {
|
|
8
|
+
value: number;
|
|
9
|
+
}, elementSelectorOnly?: string): any;
|
|
4
10
|
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
|
|
5
11
|
expect: ReturnType<typeof createHealingExpect>;
|
|
6
12
|
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixtures/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixtures/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAI/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAYpD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAIpE;AA2jBD,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,GAAG,EACpB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,IAAI,EACb,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAChC,WAAW,EAAE,MAAM,GAClB,GAAG,CAuCL;AAaD,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,OAAO,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,IAAI,EACb,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAChC,mBAAmB,CAAC,EAAE,MAAM,GAC3B,GAAG,CAkPL;AAQD,eAAO,MAAM,IAAI;YACP,UAAU,CAAC,OAAO,mBAAmB,CAAC;wGAkG9C,CAAC;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/fixtures/index.js
CHANGED
|
@@ -35,11 +35,14 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.createHealingExpect = exports.test = void 0;
|
|
37
37
|
exports.resolveFixwrightProxy = resolveFixwrightProxy;
|
|
38
|
+
exports.createFrameLocatorProxy = createFrameLocatorProxy;
|
|
39
|
+
exports.createLocatorProxy = createLocatorProxy;
|
|
38
40
|
const test_1 = require("@playwright/test");
|
|
39
41
|
const StackUtils_1 = require("../utils/StackUtils");
|
|
40
42
|
const HealingRegistry_1 = require("../engine/HealingRegistry");
|
|
41
43
|
const singleton_1 = require("../engine/singleton");
|
|
42
44
|
const expectProxy_1 = require("./expectProxy");
|
|
45
|
+
const proxyTag_1 = require("./proxyTag");
|
|
43
46
|
const fs = __importStar(require("fs"));
|
|
44
47
|
const path = __importStar(require("path"));
|
|
45
48
|
const registry = HealingRegistry_1.HealingRegistry.getInstance();
|
|
@@ -448,6 +451,10 @@ function createFrameLocatorProxy(rawFrameLocator, frameSelector, rawPage, testTi
|
|
|
448
451
|
: frameSelector;
|
|
449
452
|
return new Proxy(rawFrameLocator, {
|
|
450
453
|
get(fTarget, fProp) {
|
|
454
|
+
// Sela proxy identity — honor before the string-only fast path so
|
|
455
|
+
// symbol probes from tests / debuggers return the kind tag.
|
|
456
|
+
if (fProp === proxyTag_1.SELA_PROXY)
|
|
457
|
+
return "framelocator";
|
|
451
458
|
if (typeof fProp !== "string")
|
|
452
459
|
return Reflect.get(fTarget, fProp);
|
|
453
460
|
if (fProp === "frameLocator") {
|
|
@@ -517,6 +524,10 @@ function createLocatorProxy(rawLocator, selector, rawPage, testTitle, actionCoun
|
|
|
517
524
|
});
|
|
518
525
|
const proxy = new Proxy(rawLocator, {
|
|
519
526
|
get(target, prop, receiver) {
|
|
527
|
+
// Sela proxy identity — must be honored before any string-only branch
|
|
528
|
+
// so callers can reliably detect a Sela proxy via Symbol.for("sela.proxy").
|
|
529
|
+
if (prop === proxyTag_1.SELA_PROXY)
|
|
530
|
+
return "locator";
|
|
520
531
|
if (typeof prop !== "string")
|
|
521
532
|
return Reflect.get(target, prop, receiver);
|
|
522
533
|
// ── Internal accessors ───────────────────────────────────
|
|
@@ -661,6 +672,10 @@ exports.test = test_1.test.extend({
|
|
|
661
672
|
const unsubscribers = [];
|
|
662
673
|
const pageProxy = new Proxy(page, {
|
|
663
674
|
get(target, prop) {
|
|
675
|
+
// Sela proxy identity — honored before any other branch so callers
|
|
676
|
+
// can reliably detect a Sela page proxy via Symbol.for("sela.proxy").
|
|
677
|
+
if (prop === proxyTag_1.SELA_PROXY)
|
|
678
|
+
return "page";
|
|
664
679
|
if (prop === "_rawPage")
|
|
665
680
|
return target;
|
|
666
681
|
if (typeof prop !== "string")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const SELA_PROXY: unique symbol;
|
|
2
|
+
export type SelaProxyKind = "locator" | "framelocator" | "page" | "expect";
|
|
3
|
+
/**
|
|
4
|
+
* Returns the proxy kind if `v` is a Sela proxy, otherwise `null`.
|
|
5
|
+
*
|
|
6
|
+
* Performs no instanceof checks against Playwright internals — relies
|
|
7
|
+
* solely on the `SELA_PROXY` symbol contract. Always safe to call on
|
|
8
|
+
* arbitrary values (null/undefined/primitive → null).
|
|
9
|
+
*/
|
|
10
|
+
export declare function selaProxyKind(v: unknown): SelaProxyKind | null;
|
|
11
|
+
export declare function isSelaProxy(v: unknown, kind?: SelaProxyKind): boolean;
|
|
12
|
+
//# sourceMappingURL=proxyTag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxyTag.d.ts","sourceRoot":"","sources":["../../src/fixtures/proxyTag.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,UAAU,eAA2B,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,cAAc,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE3E;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAa9D;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAIrE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/fixtures/proxyTag.ts
|
|
3
|
+
//
|
|
4
|
+
// Single source of truth for the Sela proxy tag.
|
|
5
|
+
//
|
|
6
|
+
// Every Sela-created Proxy (locator, frame-locator, page, expect) honors a
|
|
7
|
+
// `get` trap on this symbol and returns the kind string. Consumers — the
|
|
8
|
+
// surface-diff test, internal helpers, debuggers — can identify Sela
|
|
9
|
+
// proxies without touching the brittle "_fixwrightSelector" heuristic.
|
|
10
|
+
//
|
|
11
|
+
// We use Symbol.for() (a registered symbol) so the identity survives
|
|
12
|
+
// across module boundaries when multiple bundles end up in the same
|
|
13
|
+
// process (vitest worker pools, side-loaded fixtures, etc.).
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SELA_PROXY = void 0;
|
|
16
|
+
exports.selaProxyKind = selaProxyKind;
|
|
17
|
+
exports.isSelaProxy = isSelaProxy;
|
|
18
|
+
exports.SELA_PROXY = Symbol.for("sela.proxy");
|
|
19
|
+
/**
|
|
20
|
+
* Returns the proxy kind if `v` is a Sela proxy, otherwise `null`.
|
|
21
|
+
*
|
|
22
|
+
* Performs no instanceof checks against Playwright internals — relies
|
|
23
|
+
* solely on the `SELA_PROXY` symbol contract. Always safe to call on
|
|
24
|
+
* arbitrary values (null/undefined/primitive → null).
|
|
25
|
+
*/
|
|
26
|
+
function selaProxyKind(v) {
|
|
27
|
+
if (v === null || v === undefined)
|
|
28
|
+
return null;
|
|
29
|
+
if (typeof v !== "object" && typeof v !== "function")
|
|
30
|
+
return null;
|
|
31
|
+
const tag = v[exports.SELA_PROXY];
|
|
32
|
+
if (tag === "locator" ||
|
|
33
|
+
tag === "framelocator" ||
|
|
34
|
+
tag === "page" ||
|
|
35
|
+
tag === "expect") {
|
|
36
|
+
return tag;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function isSelaProxy(v, kind) {
|
|
41
|
+
const k = selaProxyKind(v);
|
|
42
|
+
if (k === null)
|
|
43
|
+
return false;
|
|
44
|
+
return kind === undefined ? true : k === kind;
|
|
45
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export type * from '@playwright/test';
|
|
|
4
4
|
export { chromium, firefox, webkit, devices, selectors } from '@playwright/test';
|
|
5
5
|
export { test } from './fixtures/index.js';
|
|
6
6
|
export { expect } from './fixtures/moduleExpect.js';
|
|
7
|
+
export { SelaReporter } from './reporter/SelaReporter.js';
|
|
8
|
+
export type { SelaReporterOpts, ReporterEngine, ReporterLogger } from './reporter/SelaReporter.js';
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGzE,mBAAmB,kBAAkB,CAAC;AAGtC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIjF,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAI3C,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAGzE,mBAAmB,kBAAkB,CAAC;AAGtC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAIjF,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAI3C,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAKpD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Drop-in replacement for @playwright/test:
|
|
5
5
|
// import { test, expect } from 'sela-core'
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.expect = exports.test = exports.selectors = exports.devices = exports.webkit = exports.firefox = exports.chromium = exports.defineConfig = void 0;
|
|
7
|
+
exports.SelaReporter = exports.expect = exports.test = exports.selectors = exports.devices = exports.webkit = exports.firefox = exports.chromium = exports.defineConfig = void 0;
|
|
8
8
|
// Config DSL exported FIRST so `sela.config.ts` can `import { defineConfig }`
|
|
9
9
|
// without triggering the heavier fixtures/engine graph during config load.
|
|
10
10
|
var defineConfig_js_1 = require("./config/defineConfig.js");
|
|
@@ -24,3 +24,8 @@ Object.defineProperty(exports, "test", { enumerable: true, get: function () { re
|
|
|
24
24
|
// Detects page from locator, falls back to raw PW expect for non-locators
|
|
25
25
|
var moduleExpect_js_1 = require("./fixtures/moduleExpect.js");
|
|
26
26
|
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return moduleExpect_js_1.expect; } });
|
|
27
|
+
// Playwright Reporter — drop into `reporter: [['sela-core/reporter']]`
|
|
28
|
+
// in playwright.config.ts to drive heal-and-report at the reporter layer
|
|
29
|
+
// (safety net for worker crashes + Playwright failure screenshot capture).
|
|
30
|
+
var SelaReporter_js_1 = require("./reporter/SelaReporter.js");
|
|
31
|
+
Object.defineProperty(exports, "SelaReporter", { enumerable: true, get: function () { return SelaReporter_js_1.SelaReporter; } });
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Reporter, TestCase, TestResult, FullConfig, Suite, FullResult } from "@playwright/test/reporter";
|
|
2
|
+
import { HealReportService } from "../services/HealReportService.js";
|
|
3
|
+
import type { SelaEngine } from "../engine/SelaEngine.js";
|
|
4
|
+
import { type PendingPromptEntry } from "../services/PendingPromptLedger.js";
|
|
5
|
+
import { type InteractiveReviewOutcome } from "../services/InteractiveReview.js";
|
|
6
|
+
export interface ReporterEngine {
|
|
7
|
+
commitUpdates(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export interface ReporterLogger {
|
|
10
|
+
info: (msg: string) => void;
|
|
11
|
+
warn: (msg: string) => void;
|
|
12
|
+
error: (msg: string) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface SelaReporterOpts {
|
|
15
|
+
/**
|
|
16
|
+
* Skip the `ANTHROPIC_API_KEY` presence check. Tests inject this so
|
|
17
|
+
* the reporter does not surface a noisy warning when no real key is
|
|
18
|
+
* required to drive the lifecycle.
|
|
19
|
+
*/
|
|
20
|
+
skipApiKeyCheck?: boolean;
|
|
21
|
+
/** Override the engine. Defaults to `sharedEngine`. */
|
|
22
|
+
engine?: ReporterEngine | SelaEngine;
|
|
23
|
+
/** Override the report buffer. Defaults to `sharedHealReport`. */
|
|
24
|
+
report?: HealReportService;
|
|
25
|
+
/** Override the logger (defaults to `console`). */
|
|
26
|
+
logger?: ReporterLogger;
|
|
27
|
+
/** Override cwd used to locate the pending-prompts ledger. Defaults to process.cwd(). */
|
|
28
|
+
cwd?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Inject the ledger reader — tests stub this so they don't have to
|
|
31
|
+
* touch the real filesystem.
|
|
32
|
+
*/
|
|
33
|
+
readLedger?: (cwd: string) => PendingPromptEntry[];
|
|
34
|
+
/** Inject the ledger cleaner. */
|
|
35
|
+
clearLedger?: (cwd: string) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Inject the interactive review runner. Tests stub this to assert that
|
|
38
|
+
* the reporter wires the ledger entries through unchanged.
|
|
39
|
+
*/
|
|
40
|
+
runReview?: (entries: PendingPromptEntry[]) => Promise<InteractiveReviewOutcome[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Override the CI detection — true means "behave as CI" (skip the
|
|
43
|
+
* interactive review). Defaults to checking `process.env.CI`.
|
|
44
|
+
*/
|
|
45
|
+
isCi?: () => boolean;
|
|
46
|
+
}
|
|
47
|
+
export declare class SelaReporter implements Reporter {
|
|
48
|
+
private readonly engine;
|
|
49
|
+
private readonly report;
|
|
50
|
+
private readonly logger;
|
|
51
|
+
private readonly skipApiKeyCheck;
|
|
52
|
+
private readonly cwd;
|
|
53
|
+
private readonly readLedger;
|
|
54
|
+
private readonly clearLedger;
|
|
55
|
+
private readonly runReview;
|
|
56
|
+
private readonly isCi;
|
|
57
|
+
/**
|
|
58
|
+
* Screenshots that arrived before a matching FailedEvent existed in
|
|
59
|
+
* the shared report. Re-flushed once at onEnd time so worker-crash
|
|
60
|
+
* scenarios (no fixture teardown ⇒ no engine-side FailedEvent yet)
|
|
61
|
+
* still get their visual context attached.
|
|
62
|
+
*
|
|
63
|
+
* Key shape: `${normalisedSourceFile}::${testTitle}`.
|
|
64
|
+
*/
|
|
65
|
+
private readonly pendingScreenshots;
|
|
66
|
+
/**
|
|
67
|
+
* Mirror of the failures the reporter observed, kept so external
|
|
68
|
+
* consumers (and tests) can audit what the reporter saw without
|
|
69
|
+
* having to subscribe to the entire Playwright Reporter API.
|
|
70
|
+
*/
|
|
71
|
+
private readonly failures;
|
|
72
|
+
constructor(opts?: SelaReporterOpts);
|
|
73
|
+
onBegin(_config?: FullConfig, _suite?: Suite): void;
|
|
74
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
75
|
+
onEnd(_result?: FullResult): Promise<void>;
|
|
76
|
+
/** Reporter prints nothing of its own — Playwright stdio stays clean. */
|
|
77
|
+
printsToStdio(): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Walk every buffered pending screenshot and try to attach it to a
|
|
80
|
+
* now-existing FailedEvent. Successfully-attached entries are
|
|
81
|
+
* removed; unmatched entries are dropped (the matching FailedEvent
|
|
82
|
+
* never arrived — most likely worker crashed before the engine
|
|
83
|
+
* could record one).
|
|
84
|
+
*
|
|
85
|
+
* Returns the number of FailedEvents touched.
|
|
86
|
+
*/
|
|
87
|
+
flushPendingScreenshots(): number;
|
|
88
|
+
/** Test hook — read-only snapshot of recorded failures. */
|
|
89
|
+
getRecordedFailures(): ReadonlyArray<{
|
|
90
|
+
sourceFile: string;
|
|
91
|
+
sourceLine?: number;
|
|
92
|
+
testTitle: string;
|
|
93
|
+
status: string;
|
|
94
|
+
hasScreenshot: boolean;
|
|
95
|
+
}>;
|
|
96
|
+
/** Test hook — number of screenshots still waiting for a FailedEvent. */
|
|
97
|
+
getPendingScreenshotCount(): number;
|
|
98
|
+
private pendingKey;
|
|
99
|
+
private normalisePath;
|
|
100
|
+
/**
|
|
101
|
+
* Convert the first PNG attachment on a TestResult into a Base64
|
|
102
|
+
* string immediately. Prefers an in-memory `body` when Playwright
|
|
103
|
+
* already inlined the bytes (faster, no IO); falls back to reading
|
|
104
|
+
* the attachment `path` synchronously BEFORE Playwright has a chance
|
|
105
|
+
* to clean up its temp folder.
|
|
106
|
+
*/
|
|
107
|
+
private extractScreenshotBase64;
|
|
108
|
+
}
|
|
109
|
+
export default SelaReporter;
|
|
110
|
+
//# sourceMappingURL=SelaReporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelaReporter.d.ts","sourceRoot":"","sources":["../../src/reporter/SelaReporter.ts"],"names":[],"mappings":"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"}
|