donobu 5.62.0 → 5.63.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/dist/esm/lib/test/testExtension.js +3 -1
- package/dist/esm/main.d.ts +1 -0
- package/dist/esm/main.js +1 -0
- package/dist/esm/persistence/flows/FlowsPersistence.d.ts +7 -0
- package/dist/esm/persistence/flows/FlowsPersistence.js +7 -0
- package/dist/esm/utils/Logger.d.ts +22 -0
- package/dist/esm/utils/Logger.js +39 -11
- package/dist/esm/utils/PageLogListeners.d.ts +30 -10
- package/dist/esm/utils/PageLogListeners.js +37 -21
- package/dist/lib/test/testExtension.js +3 -1
- package/dist/main.d.ts +1 -0
- package/dist/main.js +1 -0
- package/dist/persistence/flows/FlowsPersistence.d.ts +7 -0
- package/dist/persistence/flows/FlowsPersistence.js +7 -0
- package/dist/utils/Logger.d.ts +22 -0
- package/dist/utils/Logger.js +39 -11
- package/dist/utils/PageLogListeners.d.ts +30 -10
- package/dist/utils/PageLogListeners.js +37 -21
- package/package.json +1 -1
|
@@ -447,7 +447,9 @@ exports.test = test_1.test.extend({
|
|
|
447
447
|
// sources are captured into the flow's logBuffer. In Studio-launched flows
|
|
448
448
|
// this is done by WebTargetInspector.initialize(), but that method is not
|
|
449
449
|
// called during test runs, so we wire the listeners up here directly.
|
|
450
|
-
|
|
450
|
+
// Bound explicitly to this flow's buffer so routing does not depend on the
|
|
451
|
+
// ambient ALS context surviving until the events fire.
|
|
452
|
+
(0, PageLogListeners_1.registerPageLogListeners)(page, { flowId, logBuffer });
|
|
451
453
|
// Bind the Playwright-provided `use` callback to an async resource so that
|
|
452
454
|
// any microtasks scheduled inside the test body keep the flow logging
|
|
453
455
|
// context. Without this, Playwright may re-use earlier async resources that
|
package/dist/esm/main.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ export * from './utils/JsonSchemaUtils';
|
|
|
79
79
|
export * from './utils/JsonUtils';
|
|
80
80
|
export * from './utils/Logger';
|
|
81
81
|
export * from './utils/MiscUtils';
|
|
82
|
+
export * from './utils/PageLogListeners';
|
|
82
83
|
export * from './utils/PlaywrightUtils';
|
|
83
84
|
export * from './utils/TargetUtils';
|
|
84
85
|
export * from './utils/TemplateInterpolator';
|
package/dist/esm/main.js
CHANGED
|
@@ -111,6 +111,7 @@ __exportStar(require("./utils/JsonSchemaUtils"), exports);
|
|
|
111
111
|
__exportStar(require("./utils/JsonUtils"), exports);
|
|
112
112
|
__exportStar(require("./utils/Logger"), exports);
|
|
113
113
|
__exportStar(require("./utils/MiscUtils"), exports);
|
|
114
|
+
__exportStar(require("./utils/PageLogListeners"), exports);
|
|
114
115
|
__exportStar(require("./utils/PlaywrightUtils"), exports);
|
|
115
116
|
__exportStar(require("./utils/TargetUtils"), exports);
|
|
116
117
|
__exportStar(require("./utils/TemplateInterpolator"), exports);
|
|
@@ -21,6 +21,13 @@ export declare const WELL_KNOWN_FILE_IDS: {
|
|
|
21
21
|
readonly donobuTestResult: "donobu-test-result.json";
|
|
22
22
|
/** Generated remediation plan consumed by Studio and admin tooling. */
|
|
23
23
|
readonly treatmentPlan: "treatment-plan.json";
|
|
24
|
+
/**
|
|
25
|
+
* Collab peer-link: written under the primary flow of a multi-user test run
|
|
26
|
+
* to enumerate the secondary flows belonging to the same run and the
|
|
27
|
+
* artifact fileIds persisted for each. Shape (versioned):
|
|
28
|
+
* `{ version: 1, peers: [{ role, flowId, files: { <label>: <fileId> } }] }`.
|
|
29
|
+
*/
|
|
30
|
+
readonly collabPeers: "donobu-collab-peers.json";
|
|
24
31
|
/** Failure-triage artifacts persisted alongside a test flow. */
|
|
25
32
|
readonly triage: {
|
|
26
33
|
/** Serialized GPT triage evidence and heuristic assessment. */
|
|
@@ -18,6 +18,13 @@ exports.WELL_KNOWN_FILE_IDS = {
|
|
|
18
18
|
donobuTestResult: 'donobu-test-result.json',
|
|
19
19
|
/** Generated remediation plan consumed by Studio and admin tooling. */
|
|
20
20
|
treatmentPlan: 'treatment-plan.json',
|
|
21
|
+
/**
|
|
22
|
+
* Collab peer-link: written under the primary flow of a multi-user test run
|
|
23
|
+
* to enumerate the secondary flows belonging to the same run and the
|
|
24
|
+
* artifact fileIds persisted for each. Shape (versioned):
|
|
25
|
+
* `{ version: 1, peers: [{ role, flowId, files: { <label>: <fileId> } }] }`.
|
|
26
|
+
*/
|
|
27
|
+
collabPeers: 'donobu-collab-peers.json',
|
|
21
28
|
/** Failure-triage artifacts persisted alongside a test flow. */
|
|
22
29
|
triage: {
|
|
23
30
|
/** Serialized GPT triage evidence and heuristic assessment. */
|
|
@@ -2,6 +2,28 @@ import { AsyncLocalStorage } from 'async_hooks';
|
|
|
2
2
|
import winston from 'winston';
|
|
3
3
|
import type { FlowLogBuffer } from '../utils/FlowLogBuffer';
|
|
4
4
|
export declare const loggingContext: AsyncLocalStorage<unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* Symbol key for flow-routing metadata carried on a log info object itself.
|
|
7
|
+
*
|
|
8
|
+
* Winston loggers are Transform streams: under backpressure, an entry may be
|
|
9
|
+
* format-transformed and delivered to transports on a *later* event-loop
|
|
10
|
+
* turn, in whatever async context happens to be live at drain time. Routing
|
|
11
|
+
* that resolves its destination via AsyncLocalStorage at delivery time
|
|
12
|
+
* therefore breaks under synchronous log bursts (a few hundred entries in
|
|
13
|
+
* one tick): entries land in the wrong flow's buffer, stamped with the wrong
|
|
14
|
+
* flowId. Callers that must guarantee attribution (e.g. page listeners bound
|
|
15
|
+
* to a specific flow — see registerPageLogListeners) attach
|
|
16
|
+
* `{ flowId, logBuffer }` under this key in the log call's meta;
|
|
17
|
+
* {@link FlowLogBufferTransport} and the flowId format prefer it over the
|
|
18
|
+
* ambient context. Symbol-keyed so it survives winston's object spreads but
|
|
19
|
+
* is invisible to JSON serialization (file transports, persisted logs).
|
|
20
|
+
*/
|
|
21
|
+
export declare const FLOW_LOG_ROUTING: unique symbol;
|
|
22
|
+
/** Value shape carried under {@link FLOW_LOG_ROUTING}. */
|
|
23
|
+
export interface FlowLogRouting {
|
|
24
|
+
flowId: string;
|
|
25
|
+
logBuffer: FlowLogBuffer;
|
|
26
|
+
}
|
|
5
27
|
/**
|
|
6
28
|
* ###################################################################################
|
|
7
29
|
* # WARNING! Only use this function within the context of Playwright test fixtures! #
|
package/dist/esm/utils/Logger.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.networkLogger = exports.browserLogger = exports.accessLogger = exports.appLogger = exports.loggingContext = void 0;
|
|
6
|
+
exports.networkLogger = exports.browserLogger = exports.accessLogger = exports.appLogger = exports.FLOW_LOG_ROUTING = exports.loggingContext = void 0;
|
|
7
7
|
exports.setProcessLocalFlowId = setProcessLocalFlowId;
|
|
8
8
|
exports.setProcessLocalLogBuffer = setProcessLocalLogBuffer;
|
|
9
9
|
exports.logErrorWithoutStack = logErrorWithoutStack;
|
|
@@ -69,10 +69,32 @@ const errorAwareFormat = winston_1.default.format((info) => {
|
|
|
69
69
|
});
|
|
70
70
|
exports.loggingContext = new async_hooks_1.AsyncLocalStorage();
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
72
|
+
* Symbol key for flow-routing metadata carried on a log info object itself.
|
|
73
|
+
*
|
|
74
|
+
* Winston loggers are Transform streams: under backpressure, an entry may be
|
|
75
|
+
* format-transformed and delivered to transports on a *later* event-loop
|
|
76
|
+
* turn, in whatever async context happens to be live at drain time. Routing
|
|
77
|
+
* that resolves its destination via AsyncLocalStorage at delivery time
|
|
78
|
+
* therefore breaks under synchronous log bursts (a few hundred entries in
|
|
79
|
+
* one tick): entries land in the wrong flow's buffer, stamped with the wrong
|
|
80
|
+
* flowId. Callers that must guarantee attribution (e.g. page listeners bound
|
|
81
|
+
* to a specific flow — see registerPageLogListeners) attach
|
|
82
|
+
* `{ flowId, logBuffer }` under this key in the log call's meta;
|
|
83
|
+
* {@link FlowLogBufferTransport} and the flowId format prefer it over the
|
|
84
|
+
* ambient context. Symbol-keyed so it survives winston's object spreads but
|
|
85
|
+
* is invisible to JSON serialization (file transports, persisted logs).
|
|
86
|
+
*/
|
|
87
|
+
exports.FLOW_LOG_ROUTING = Symbol('donobu.flowLogRouting');
|
|
88
|
+
function getInfoRouting(info) {
|
|
89
|
+
return info[exports.FLOW_LOG_ROUTING];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Custom Winston transport that routes log entries into a per-flow
|
|
93
|
+
* {@link FlowLogBuffer}: the one carried on the entry itself via
|
|
94
|
+
* {@link FLOW_LOG_ROUTING} when present, otherwise the one stored in the
|
|
95
|
+
* {@link loggingContext} async local storage. Each logger instance creates
|
|
96
|
+
* its own transport tagged with the appropriate source so the buffer knows
|
|
97
|
+
* the origin.
|
|
76
98
|
*/
|
|
77
99
|
class FlowLogBufferTransport extends winston_transport_1.default {
|
|
78
100
|
constructor(source) {
|
|
@@ -81,9 +103,13 @@ class FlowLogBufferTransport extends winston_transport_1.default {
|
|
|
81
103
|
}
|
|
82
104
|
log(info, callback) {
|
|
83
105
|
const store = exports.loggingContext.getStore();
|
|
84
|
-
const buffer =
|
|
106
|
+
const buffer = getInfoRouting(info)?.logBuffer ||
|
|
107
|
+
store?.logBuffer ||
|
|
108
|
+
processLocalLogBuffer;
|
|
85
109
|
if (buffer) {
|
|
86
|
-
|
|
110
|
+
const entry = { ...info, source: this.source };
|
|
111
|
+
delete entry[exports.FLOW_LOG_ROUTING];
|
|
112
|
+
buffer.push(entry);
|
|
87
113
|
}
|
|
88
114
|
callback();
|
|
89
115
|
}
|
|
@@ -111,12 +137,14 @@ function setProcessLocalFlowId(flowId) {
|
|
|
111
137
|
function setProcessLocalLogBuffer(buffer) {
|
|
112
138
|
processLocalLogBuffer = buffer;
|
|
113
139
|
}
|
|
114
|
-
// Format to add the currently running flow's ID (if any)
|
|
140
|
+
// Format to add the currently running flow's ID (if any). Prefers routing
|
|
141
|
+
// carried on the entry itself (immune to winston's deferred transform under
|
|
142
|
+
// backpressure — see FLOW_LOG_ROUTING), then the AsyncLocalStorage store,
|
|
143
|
+
// then the process-local slot for logs emitted before the ALS store is
|
|
144
|
+
// available (see note above).
|
|
115
145
|
const flowIdFormat = winston_1.default.format((info) => {
|
|
116
146
|
const store = exports.loggingContext.getStore();
|
|
117
|
-
|
|
118
|
-
// logs emitted before the ALS store is available (see note above).
|
|
119
|
-
const flowId = store?.flowId || processLocalFlowId || '-';
|
|
147
|
+
const flowId = getInfoRouting(info)?.flowId || store?.flowId || processLocalFlowId || '-';
|
|
120
148
|
return {
|
|
121
149
|
...info,
|
|
122
150
|
flowId,
|
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
import type { Page } from 'playwright';
|
|
2
|
+
import type { FlowLogBuffer } from './FlowLogBuffer';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
* Explicit log destination for {@link registerPageLogListeners}.
|
|
5
|
+
*/
|
|
6
|
+
export interface PageLogListenerOptions {
|
|
7
|
+
/** Flow ID stamped onto every captured entry. */
|
|
8
|
+
flowId: string;
|
|
9
|
+
/** Buffer the captured entries are routed into. */
|
|
10
|
+
logBuffer: FlowLogBuffer;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Registers Playwright page event listeners that route browser console
|
|
14
|
+
* messages and network request/response data to the {@link browserLogger}
|
|
15
|
+
* and {@link networkLogger} Winston loggers, whose
|
|
16
|
+
* {@link FlowLogBufferTransport} captures entries into a per-flow log
|
|
17
|
+
* buffer. Called from both {@link WebTargetInspector.handleNewPage}
|
|
18
|
+
* (Studio-launched flows) and the Playwright test extension fixture.
|
|
19
|
+
*
|
|
20
|
+
* By default, entries are routed by the ambient AsyncLocalStorage logging
|
|
21
|
+
* context (or the process-local fallback). Pass `opts` to bind this page's
|
|
22
|
+
* listeners to an explicit flow + buffer instead. That binding is required
|
|
23
|
+
* whenever one test drives multiple pages with distinct flows (e.g.
|
|
24
|
+
* multi-user collab tests): the ambient context belongs to the primary
|
|
25
|
+
* page's flow, so an unbound secondary page would leak its entries —
|
|
26
|
+
* stamped with the wrong flowId — into the primary flow's buffer.
|
|
9
27
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
28
|
+
* The binding is stamped onto each entry's meta at emit time (see
|
|
29
|
+
* {@link FLOW_LOG_ROUTING}) rather than scoped via AsyncLocalStorage:
|
|
30
|
+
* winston defers format + transport delivery under backpressure, so
|
|
31
|
+
* ALS-based scoping would resolve against whatever context is live at drain
|
|
32
|
+
* time and silently misroute entries during synchronous log bursts.
|
|
13
33
|
*/
|
|
14
|
-
export declare function registerPageLogListeners(page: Page): void;
|
|
34
|
+
export declare function registerPageLogListeners(page: Page, opts?: PageLogListenerOptions): void;
|
|
15
35
|
//# sourceMappingURL=PageLogListeners.d.ts.map
|
|
@@ -3,18 +3,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerPageLogListeners = registerPageLogListeners;
|
|
4
4
|
const Logger_1 = require("./Logger");
|
|
5
5
|
/**
|
|
6
|
-
* Registers Playwright page event listeners that route browser console
|
|
7
|
-
* and network request/response data to the {@link browserLogger}
|
|
8
|
-
* {@link networkLogger} Winston loggers
|
|
9
|
-
* {@link FlowLogBufferTransport}
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* Registers Playwright page event listeners that route browser console
|
|
7
|
+
* messages and network request/response data to the {@link browserLogger}
|
|
8
|
+
* and {@link networkLogger} Winston loggers, whose
|
|
9
|
+
* {@link FlowLogBufferTransport} captures entries into a per-flow log
|
|
10
|
+
* buffer. Called from both {@link WebTargetInspector.handleNewPage}
|
|
11
|
+
* (Studio-launched flows) and the Playwright test extension fixture.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* (
|
|
15
|
-
*
|
|
13
|
+
* By default, entries are routed by the ambient AsyncLocalStorage logging
|
|
14
|
+
* context (or the process-local fallback). Pass `opts` to bind this page's
|
|
15
|
+
* listeners to an explicit flow + buffer instead. That binding is required
|
|
16
|
+
* whenever one test drives multiple pages with distinct flows (e.g.
|
|
17
|
+
* multi-user collab tests): the ambient context belongs to the primary
|
|
18
|
+
* page's flow, so an unbound secondary page would leak its entries —
|
|
19
|
+
* stamped with the wrong flowId — into the primary flow's buffer.
|
|
20
|
+
*
|
|
21
|
+
* The binding is stamped onto each entry's meta at emit time (see
|
|
22
|
+
* {@link FLOW_LOG_ROUTING}) rather than scoped via AsyncLocalStorage:
|
|
23
|
+
* winston defers format + transport delivery under backpressure, so
|
|
24
|
+
* ALS-based scoping would resolve against whatever context is live at drain
|
|
25
|
+
* time and silently misroute entries during synchronous log bursts.
|
|
16
26
|
*/
|
|
17
|
-
function registerPageLogListeners(page) {
|
|
27
|
+
function registerPageLogListeners(page, opts) {
|
|
28
|
+
const routing = opts
|
|
29
|
+
? { [Logger_1.FLOW_LOG_ROUTING]: { flowId: opts.flowId, logBuffer: opts.logBuffer } }
|
|
30
|
+
: undefined;
|
|
31
|
+
// Merge the routing stamp into a log call's meta. Leaves the unbound
|
|
32
|
+
// call path byte-identical (meta stays undefined when both are absent).
|
|
33
|
+
const stamp = (meta) => routing ? { ...meta, ...routing } : meta;
|
|
18
34
|
page.on('console', (msg) => {
|
|
19
35
|
const { url, lineNumber, columnNumber } = msg.location();
|
|
20
36
|
const hasSourceLocation = lineNumber !== 0 || columnNumber !== 0;
|
|
@@ -25,24 +41,24 @@ function registerPageLogListeners(page) {
|
|
|
25
41
|
: undefined;
|
|
26
42
|
switch (msg.type()) {
|
|
27
43
|
case 'error':
|
|
28
|
-
Logger_1.browserLogger.error(msg.text(), meta);
|
|
44
|
+
Logger_1.browserLogger.error(msg.text(), stamp(meta));
|
|
29
45
|
break;
|
|
30
46
|
case 'warning':
|
|
31
|
-
Logger_1.browserLogger.warn(msg.text(), meta);
|
|
47
|
+
Logger_1.browserLogger.warn(msg.text(), stamp(meta));
|
|
32
48
|
break;
|
|
33
49
|
case 'debug':
|
|
34
|
-
Logger_1.browserLogger.debug(msg.text(), meta);
|
|
50
|
+
Logger_1.browserLogger.debug(msg.text(), stamp(meta));
|
|
35
51
|
break;
|
|
36
52
|
default:
|
|
37
|
-
Logger_1.browserLogger.info(msg.text(), meta);
|
|
53
|
+
Logger_1.browserLogger.info(msg.text(), stamp(meta));
|
|
38
54
|
break;
|
|
39
55
|
}
|
|
40
56
|
});
|
|
41
57
|
page.on('pageerror', (error) => {
|
|
42
|
-
Logger_1.browserLogger.error(error.message, {
|
|
58
|
+
Logger_1.browserLogger.error(error.message, stamp({
|
|
43
59
|
stack: error.stack,
|
|
44
60
|
url: page.url(),
|
|
45
|
-
});
|
|
61
|
+
}));
|
|
46
62
|
});
|
|
47
63
|
// Fallback timing: track request start times keyed by the Request object
|
|
48
64
|
// itself (via WeakMap) so concurrent requests to the same URL don't collide.
|
|
@@ -71,24 +87,24 @@ function registerPageLogListeners(page) {
|
|
|
71
87
|
resourceType: request.resourceType(),
|
|
72
88
|
};
|
|
73
89
|
if (status >= 500) {
|
|
74
|
-
Logger_1.networkLogger.error(request.url(), meta);
|
|
90
|
+
Logger_1.networkLogger.error(request.url(), stamp(meta));
|
|
75
91
|
}
|
|
76
92
|
else if (status >= 400) {
|
|
77
|
-
Logger_1.networkLogger.warn(request.url(), meta);
|
|
93
|
+
Logger_1.networkLogger.warn(request.url(), stamp(meta));
|
|
78
94
|
}
|
|
79
95
|
else {
|
|
80
|
-
Logger_1.networkLogger.info(request.url(), meta);
|
|
96
|
+
Logger_1.networkLogger.info(request.url(), stamp(meta));
|
|
81
97
|
}
|
|
82
98
|
});
|
|
83
99
|
page.on('requestfailed', (request) => {
|
|
84
100
|
const duration = getDuration(request);
|
|
85
|
-
Logger_1.networkLogger.error(`${request.url()} FAILED: ${request.failure()?.errorText}`, {
|
|
101
|
+
Logger_1.networkLogger.error(`${request.url()} FAILED: ${request.failure()?.errorText}`, stamp({
|
|
86
102
|
method: request.method(),
|
|
87
103
|
url: request.url(),
|
|
88
104
|
resourceType: request.resourceType(),
|
|
89
105
|
failureReason: request.failure()?.errorText,
|
|
90
106
|
duration,
|
|
91
|
-
});
|
|
107
|
+
}));
|
|
92
108
|
});
|
|
93
109
|
}
|
|
94
110
|
//# sourceMappingURL=PageLogListeners.js.map
|
|
@@ -447,7 +447,9 @@ exports.test = test_1.test.extend({
|
|
|
447
447
|
// sources are captured into the flow's logBuffer. In Studio-launched flows
|
|
448
448
|
// this is done by WebTargetInspector.initialize(), but that method is not
|
|
449
449
|
// called during test runs, so we wire the listeners up here directly.
|
|
450
|
-
|
|
450
|
+
// Bound explicitly to this flow's buffer so routing does not depend on the
|
|
451
|
+
// ambient ALS context surviving until the events fire.
|
|
452
|
+
(0, PageLogListeners_1.registerPageLogListeners)(page, { flowId, logBuffer });
|
|
451
453
|
// Bind the Playwright-provided `use` callback to an async resource so that
|
|
452
454
|
// any microtasks scheduled inside the test body keep the flow logging
|
|
453
455
|
// context. Without this, Playwright may re-use earlier async resources that
|
package/dist/main.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ export * from './utils/JsonSchemaUtils';
|
|
|
79
79
|
export * from './utils/JsonUtils';
|
|
80
80
|
export * from './utils/Logger';
|
|
81
81
|
export * from './utils/MiscUtils';
|
|
82
|
+
export * from './utils/PageLogListeners';
|
|
82
83
|
export * from './utils/PlaywrightUtils';
|
|
83
84
|
export * from './utils/TargetUtils';
|
|
84
85
|
export * from './utils/TemplateInterpolator';
|
package/dist/main.js
CHANGED
|
@@ -111,6 +111,7 @@ __exportStar(require("./utils/JsonSchemaUtils"), exports);
|
|
|
111
111
|
__exportStar(require("./utils/JsonUtils"), exports);
|
|
112
112
|
__exportStar(require("./utils/Logger"), exports);
|
|
113
113
|
__exportStar(require("./utils/MiscUtils"), exports);
|
|
114
|
+
__exportStar(require("./utils/PageLogListeners"), exports);
|
|
114
115
|
__exportStar(require("./utils/PlaywrightUtils"), exports);
|
|
115
116
|
__exportStar(require("./utils/TargetUtils"), exports);
|
|
116
117
|
__exportStar(require("./utils/TemplateInterpolator"), exports);
|
|
@@ -21,6 +21,13 @@ export declare const WELL_KNOWN_FILE_IDS: {
|
|
|
21
21
|
readonly donobuTestResult: "donobu-test-result.json";
|
|
22
22
|
/** Generated remediation plan consumed by Studio and admin tooling. */
|
|
23
23
|
readonly treatmentPlan: "treatment-plan.json";
|
|
24
|
+
/**
|
|
25
|
+
* Collab peer-link: written under the primary flow of a multi-user test run
|
|
26
|
+
* to enumerate the secondary flows belonging to the same run and the
|
|
27
|
+
* artifact fileIds persisted for each. Shape (versioned):
|
|
28
|
+
* `{ version: 1, peers: [{ role, flowId, files: { <label>: <fileId> } }] }`.
|
|
29
|
+
*/
|
|
30
|
+
readonly collabPeers: "donobu-collab-peers.json";
|
|
24
31
|
/** Failure-triage artifacts persisted alongside a test flow. */
|
|
25
32
|
readonly triage: {
|
|
26
33
|
/** Serialized GPT triage evidence and heuristic assessment. */
|
|
@@ -18,6 +18,13 @@ exports.WELL_KNOWN_FILE_IDS = {
|
|
|
18
18
|
donobuTestResult: 'donobu-test-result.json',
|
|
19
19
|
/** Generated remediation plan consumed by Studio and admin tooling. */
|
|
20
20
|
treatmentPlan: 'treatment-plan.json',
|
|
21
|
+
/**
|
|
22
|
+
* Collab peer-link: written under the primary flow of a multi-user test run
|
|
23
|
+
* to enumerate the secondary flows belonging to the same run and the
|
|
24
|
+
* artifact fileIds persisted for each. Shape (versioned):
|
|
25
|
+
* `{ version: 1, peers: [{ role, flowId, files: { <label>: <fileId> } }] }`.
|
|
26
|
+
*/
|
|
27
|
+
collabPeers: 'donobu-collab-peers.json',
|
|
21
28
|
/** Failure-triage artifacts persisted alongside a test flow. */
|
|
22
29
|
triage: {
|
|
23
30
|
/** Serialized GPT triage evidence and heuristic assessment. */
|
package/dist/utils/Logger.d.ts
CHANGED
|
@@ -2,6 +2,28 @@ import { AsyncLocalStorage } from 'async_hooks';
|
|
|
2
2
|
import winston from 'winston';
|
|
3
3
|
import type { FlowLogBuffer } from '../utils/FlowLogBuffer';
|
|
4
4
|
export declare const loggingContext: AsyncLocalStorage<unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* Symbol key for flow-routing metadata carried on a log info object itself.
|
|
7
|
+
*
|
|
8
|
+
* Winston loggers are Transform streams: under backpressure, an entry may be
|
|
9
|
+
* format-transformed and delivered to transports on a *later* event-loop
|
|
10
|
+
* turn, in whatever async context happens to be live at drain time. Routing
|
|
11
|
+
* that resolves its destination via AsyncLocalStorage at delivery time
|
|
12
|
+
* therefore breaks under synchronous log bursts (a few hundred entries in
|
|
13
|
+
* one tick): entries land in the wrong flow's buffer, stamped with the wrong
|
|
14
|
+
* flowId. Callers that must guarantee attribution (e.g. page listeners bound
|
|
15
|
+
* to a specific flow — see registerPageLogListeners) attach
|
|
16
|
+
* `{ flowId, logBuffer }` under this key in the log call's meta;
|
|
17
|
+
* {@link FlowLogBufferTransport} and the flowId format prefer it over the
|
|
18
|
+
* ambient context. Symbol-keyed so it survives winston's object spreads but
|
|
19
|
+
* is invisible to JSON serialization (file transports, persisted logs).
|
|
20
|
+
*/
|
|
21
|
+
export declare const FLOW_LOG_ROUTING: unique symbol;
|
|
22
|
+
/** Value shape carried under {@link FLOW_LOG_ROUTING}. */
|
|
23
|
+
export interface FlowLogRouting {
|
|
24
|
+
flowId: string;
|
|
25
|
+
logBuffer: FlowLogBuffer;
|
|
26
|
+
}
|
|
5
27
|
/**
|
|
6
28
|
* ###################################################################################
|
|
7
29
|
* # WARNING! Only use this function within the context of Playwright test fixtures! #
|
package/dist/utils/Logger.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.networkLogger = exports.browserLogger = exports.accessLogger = exports.appLogger = exports.loggingContext = void 0;
|
|
6
|
+
exports.networkLogger = exports.browserLogger = exports.accessLogger = exports.appLogger = exports.FLOW_LOG_ROUTING = exports.loggingContext = void 0;
|
|
7
7
|
exports.setProcessLocalFlowId = setProcessLocalFlowId;
|
|
8
8
|
exports.setProcessLocalLogBuffer = setProcessLocalLogBuffer;
|
|
9
9
|
exports.logErrorWithoutStack = logErrorWithoutStack;
|
|
@@ -69,10 +69,32 @@ const errorAwareFormat = winston_1.default.format((info) => {
|
|
|
69
69
|
});
|
|
70
70
|
exports.loggingContext = new async_hooks_1.AsyncLocalStorage();
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
72
|
+
* Symbol key for flow-routing metadata carried on a log info object itself.
|
|
73
|
+
*
|
|
74
|
+
* Winston loggers are Transform streams: under backpressure, an entry may be
|
|
75
|
+
* format-transformed and delivered to transports on a *later* event-loop
|
|
76
|
+
* turn, in whatever async context happens to be live at drain time. Routing
|
|
77
|
+
* that resolves its destination via AsyncLocalStorage at delivery time
|
|
78
|
+
* therefore breaks under synchronous log bursts (a few hundred entries in
|
|
79
|
+
* one tick): entries land in the wrong flow's buffer, stamped with the wrong
|
|
80
|
+
* flowId. Callers that must guarantee attribution (e.g. page listeners bound
|
|
81
|
+
* to a specific flow — see registerPageLogListeners) attach
|
|
82
|
+
* `{ flowId, logBuffer }` under this key in the log call's meta;
|
|
83
|
+
* {@link FlowLogBufferTransport} and the flowId format prefer it over the
|
|
84
|
+
* ambient context. Symbol-keyed so it survives winston's object spreads but
|
|
85
|
+
* is invisible to JSON serialization (file transports, persisted logs).
|
|
86
|
+
*/
|
|
87
|
+
exports.FLOW_LOG_ROUTING = Symbol('donobu.flowLogRouting');
|
|
88
|
+
function getInfoRouting(info) {
|
|
89
|
+
return info[exports.FLOW_LOG_ROUTING];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Custom Winston transport that routes log entries into a per-flow
|
|
93
|
+
* {@link FlowLogBuffer}: the one carried on the entry itself via
|
|
94
|
+
* {@link FLOW_LOG_ROUTING} when present, otherwise the one stored in the
|
|
95
|
+
* {@link loggingContext} async local storage. Each logger instance creates
|
|
96
|
+
* its own transport tagged with the appropriate source so the buffer knows
|
|
97
|
+
* the origin.
|
|
76
98
|
*/
|
|
77
99
|
class FlowLogBufferTransport extends winston_transport_1.default {
|
|
78
100
|
constructor(source) {
|
|
@@ -81,9 +103,13 @@ class FlowLogBufferTransport extends winston_transport_1.default {
|
|
|
81
103
|
}
|
|
82
104
|
log(info, callback) {
|
|
83
105
|
const store = exports.loggingContext.getStore();
|
|
84
|
-
const buffer =
|
|
106
|
+
const buffer = getInfoRouting(info)?.logBuffer ||
|
|
107
|
+
store?.logBuffer ||
|
|
108
|
+
processLocalLogBuffer;
|
|
85
109
|
if (buffer) {
|
|
86
|
-
|
|
110
|
+
const entry = { ...info, source: this.source };
|
|
111
|
+
delete entry[exports.FLOW_LOG_ROUTING];
|
|
112
|
+
buffer.push(entry);
|
|
87
113
|
}
|
|
88
114
|
callback();
|
|
89
115
|
}
|
|
@@ -111,12 +137,14 @@ function setProcessLocalFlowId(flowId) {
|
|
|
111
137
|
function setProcessLocalLogBuffer(buffer) {
|
|
112
138
|
processLocalLogBuffer = buffer;
|
|
113
139
|
}
|
|
114
|
-
// Format to add the currently running flow's ID (if any)
|
|
140
|
+
// Format to add the currently running flow's ID (if any). Prefers routing
|
|
141
|
+
// carried on the entry itself (immune to winston's deferred transform under
|
|
142
|
+
// backpressure — see FLOW_LOG_ROUTING), then the AsyncLocalStorage store,
|
|
143
|
+
// then the process-local slot for logs emitted before the ALS store is
|
|
144
|
+
// available (see note above).
|
|
115
145
|
const flowIdFormat = winston_1.default.format((info) => {
|
|
116
146
|
const store = exports.loggingContext.getStore();
|
|
117
|
-
|
|
118
|
-
// logs emitted before the ALS store is available (see note above).
|
|
119
|
-
const flowId = store?.flowId || processLocalFlowId || '-';
|
|
147
|
+
const flowId = getInfoRouting(info)?.flowId || store?.flowId || processLocalFlowId || '-';
|
|
120
148
|
return {
|
|
121
149
|
...info,
|
|
122
150
|
flowId,
|
|
@@ -1,15 +1,35 @@
|
|
|
1
1
|
import type { Page } from 'playwright';
|
|
2
|
+
import type { FlowLogBuffer } from './FlowLogBuffer';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
* Explicit log destination for {@link registerPageLogListeners}.
|
|
5
|
+
*/
|
|
6
|
+
export interface PageLogListenerOptions {
|
|
7
|
+
/** Flow ID stamped onto every captured entry. */
|
|
8
|
+
flowId: string;
|
|
9
|
+
/** Buffer the captured entries are routed into. */
|
|
10
|
+
logBuffer: FlowLogBuffer;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Registers Playwright page event listeners that route browser console
|
|
14
|
+
* messages and network request/response data to the {@link browserLogger}
|
|
15
|
+
* and {@link networkLogger} Winston loggers, whose
|
|
16
|
+
* {@link FlowLogBufferTransport} captures entries into a per-flow log
|
|
17
|
+
* buffer. Called from both {@link WebTargetInspector.handleNewPage}
|
|
18
|
+
* (Studio-launched flows) and the Playwright test extension fixture.
|
|
19
|
+
*
|
|
20
|
+
* By default, entries are routed by the ambient AsyncLocalStorage logging
|
|
21
|
+
* context (or the process-local fallback). Pass `opts` to bind this page's
|
|
22
|
+
* listeners to an explicit flow + buffer instead. That binding is required
|
|
23
|
+
* whenever one test drives multiple pages with distinct flows (e.g.
|
|
24
|
+
* multi-user collab tests): the ambient context belongs to the primary
|
|
25
|
+
* page's flow, so an unbound secondary page would leak its entries —
|
|
26
|
+
* stamped with the wrong flowId — into the primary flow's buffer.
|
|
9
27
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
28
|
+
* The binding is stamped onto each entry's meta at emit time (see
|
|
29
|
+
* {@link FLOW_LOG_ROUTING}) rather than scoped via AsyncLocalStorage:
|
|
30
|
+
* winston defers format + transport delivery under backpressure, so
|
|
31
|
+
* ALS-based scoping would resolve against whatever context is live at drain
|
|
32
|
+
* time and silently misroute entries during synchronous log bursts.
|
|
13
33
|
*/
|
|
14
|
-
export declare function registerPageLogListeners(page: Page): void;
|
|
34
|
+
export declare function registerPageLogListeners(page: Page, opts?: PageLogListenerOptions): void;
|
|
15
35
|
//# sourceMappingURL=PageLogListeners.d.ts.map
|
|
@@ -3,18 +3,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerPageLogListeners = registerPageLogListeners;
|
|
4
4
|
const Logger_1 = require("./Logger");
|
|
5
5
|
/**
|
|
6
|
-
* Registers Playwright page event listeners that route browser console
|
|
7
|
-
* and network request/response data to the {@link browserLogger}
|
|
8
|
-
* {@link networkLogger} Winston loggers
|
|
9
|
-
* {@link FlowLogBufferTransport}
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* Registers Playwright page event listeners that route browser console
|
|
7
|
+
* messages and network request/response data to the {@link browserLogger}
|
|
8
|
+
* and {@link networkLogger} Winston loggers, whose
|
|
9
|
+
* {@link FlowLogBufferTransport} captures entries into a per-flow log
|
|
10
|
+
* buffer. Called from both {@link WebTargetInspector.handleNewPage}
|
|
11
|
+
* (Studio-launched flows) and the Playwright test extension fixture.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* (
|
|
15
|
-
*
|
|
13
|
+
* By default, entries are routed by the ambient AsyncLocalStorage logging
|
|
14
|
+
* context (or the process-local fallback). Pass `opts` to bind this page's
|
|
15
|
+
* listeners to an explicit flow + buffer instead. That binding is required
|
|
16
|
+
* whenever one test drives multiple pages with distinct flows (e.g.
|
|
17
|
+
* multi-user collab tests): the ambient context belongs to the primary
|
|
18
|
+
* page's flow, so an unbound secondary page would leak its entries —
|
|
19
|
+
* stamped with the wrong flowId — into the primary flow's buffer.
|
|
20
|
+
*
|
|
21
|
+
* The binding is stamped onto each entry's meta at emit time (see
|
|
22
|
+
* {@link FLOW_LOG_ROUTING}) rather than scoped via AsyncLocalStorage:
|
|
23
|
+
* winston defers format + transport delivery under backpressure, so
|
|
24
|
+
* ALS-based scoping would resolve against whatever context is live at drain
|
|
25
|
+
* time and silently misroute entries during synchronous log bursts.
|
|
16
26
|
*/
|
|
17
|
-
function registerPageLogListeners(page) {
|
|
27
|
+
function registerPageLogListeners(page, opts) {
|
|
28
|
+
const routing = opts
|
|
29
|
+
? { [Logger_1.FLOW_LOG_ROUTING]: { flowId: opts.flowId, logBuffer: opts.logBuffer } }
|
|
30
|
+
: undefined;
|
|
31
|
+
// Merge the routing stamp into a log call's meta. Leaves the unbound
|
|
32
|
+
// call path byte-identical (meta stays undefined when both are absent).
|
|
33
|
+
const stamp = (meta) => routing ? { ...meta, ...routing } : meta;
|
|
18
34
|
page.on('console', (msg) => {
|
|
19
35
|
const { url, lineNumber, columnNumber } = msg.location();
|
|
20
36
|
const hasSourceLocation = lineNumber !== 0 || columnNumber !== 0;
|
|
@@ -25,24 +41,24 @@ function registerPageLogListeners(page) {
|
|
|
25
41
|
: undefined;
|
|
26
42
|
switch (msg.type()) {
|
|
27
43
|
case 'error':
|
|
28
|
-
Logger_1.browserLogger.error(msg.text(), meta);
|
|
44
|
+
Logger_1.browserLogger.error(msg.text(), stamp(meta));
|
|
29
45
|
break;
|
|
30
46
|
case 'warning':
|
|
31
|
-
Logger_1.browserLogger.warn(msg.text(), meta);
|
|
47
|
+
Logger_1.browserLogger.warn(msg.text(), stamp(meta));
|
|
32
48
|
break;
|
|
33
49
|
case 'debug':
|
|
34
|
-
Logger_1.browserLogger.debug(msg.text(), meta);
|
|
50
|
+
Logger_1.browserLogger.debug(msg.text(), stamp(meta));
|
|
35
51
|
break;
|
|
36
52
|
default:
|
|
37
|
-
Logger_1.browserLogger.info(msg.text(), meta);
|
|
53
|
+
Logger_1.browserLogger.info(msg.text(), stamp(meta));
|
|
38
54
|
break;
|
|
39
55
|
}
|
|
40
56
|
});
|
|
41
57
|
page.on('pageerror', (error) => {
|
|
42
|
-
Logger_1.browserLogger.error(error.message, {
|
|
58
|
+
Logger_1.browserLogger.error(error.message, stamp({
|
|
43
59
|
stack: error.stack,
|
|
44
60
|
url: page.url(),
|
|
45
|
-
});
|
|
61
|
+
}));
|
|
46
62
|
});
|
|
47
63
|
// Fallback timing: track request start times keyed by the Request object
|
|
48
64
|
// itself (via WeakMap) so concurrent requests to the same URL don't collide.
|
|
@@ -71,24 +87,24 @@ function registerPageLogListeners(page) {
|
|
|
71
87
|
resourceType: request.resourceType(),
|
|
72
88
|
};
|
|
73
89
|
if (status >= 500) {
|
|
74
|
-
Logger_1.networkLogger.error(request.url(), meta);
|
|
90
|
+
Logger_1.networkLogger.error(request.url(), stamp(meta));
|
|
75
91
|
}
|
|
76
92
|
else if (status >= 400) {
|
|
77
|
-
Logger_1.networkLogger.warn(request.url(), meta);
|
|
93
|
+
Logger_1.networkLogger.warn(request.url(), stamp(meta));
|
|
78
94
|
}
|
|
79
95
|
else {
|
|
80
|
-
Logger_1.networkLogger.info(request.url(), meta);
|
|
96
|
+
Logger_1.networkLogger.info(request.url(), stamp(meta));
|
|
81
97
|
}
|
|
82
98
|
});
|
|
83
99
|
page.on('requestfailed', (request) => {
|
|
84
100
|
const duration = getDuration(request);
|
|
85
|
-
Logger_1.networkLogger.error(`${request.url()} FAILED: ${request.failure()?.errorText}`, {
|
|
101
|
+
Logger_1.networkLogger.error(`${request.url()} FAILED: ${request.failure()?.errorText}`, stamp({
|
|
86
102
|
method: request.method(),
|
|
87
103
|
url: request.url(),
|
|
88
104
|
resourceType: request.resourceType(),
|
|
89
105
|
failureReason: request.failure()?.errorText,
|
|
90
106
|
duration,
|
|
91
|
-
});
|
|
107
|
+
}));
|
|
92
108
|
});
|
|
93
109
|
}
|
|
94
110
|
//# sourceMappingURL=PageLogListeners.js.map
|