@elliemae/encw-leak-runner 1.0.13 → 1.0.15
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/CHANGELOG.md +23 -0
- package/README.md +63 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/leak-runner.js +258 -29
- package/dist/cjs/browser/heapMemoryProfiler.js +182 -0
- package/dist/cjs/browser/iframeHeapProfiler.js +2 -2
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/runner/scenarioRunner.js +9 -6
- package/dist/cjs/scenarios/index.js +7 -4
- package/dist/cjs/scenarios/one-admin/index.js +5 -1
- package/dist/cjs/scenarios/pipeline/index.js +31 -0
- package/dist/cjs/scenarios/pipeline/page-models/PipelinePageModel.js +52 -0
- package/dist/cjs/scenarios/pipeline/page-models/index.js +24 -0
- package/dist/cjs/scenarios/pipeline/pipeline-task-navigation.scenario.js +56 -0
- package/dist/esm/browser/heapMemoryProfiler.js +152 -0
- package/dist/esm/browser/iframeHeapProfiler.js +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/runner/scenarioRunner.js +9 -6
- package/dist/esm/scenarios/index.js +8 -5
- package/dist/esm/scenarios/one-admin/index.js +5 -1
- package/dist/esm/scenarios/pipeline/index.js +11 -0
- package/dist/esm/scenarios/pipeline/page-models/PipelinePageModel.js +32 -0
- package/dist/esm/scenarios/pipeline/page-models/index.js +4 -0
- package/dist/esm/scenarios/pipeline/pipeline-task-navigation.scenario.js +36 -0
- package/dist/types/lib/browser/heapMemoryProfiler.d.ts +149 -0
- package/dist/types/lib/browser/iframeHeapProfiler.d.ts +1 -1
- package/dist/types/lib/browser/tests/heapMemoryProfiler.test.d.ts +1 -0
- package/dist/types/lib/config/runnerConfigSchema.d.ts +6 -6
- package/dist/types/lib/index.d.ts +2 -0
- package/dist/types/lib/scenarios/one-admin/index.d.ts +2 -2
- package/dist/types/lib/scenarios/pipeline/index.d.ts +2 -0
- package/dist/types/lib/scenarios/pipeline/page-models/PipelinePageModel.d.ts +14 -0
- package/dist/types/lib/scenarios/pipeline/page-models/index.d.ts +1 -0
- package/dist/types/lib/scenarios/pipeline/pipeline-task-navigation.scenario.d.ts +2 -0
- package/leak-runner.config.json +1 -1
- package/lib/browser/heapMemoryProfiler.ts +284 -0
- package/lib/browser/iframeHeapProfiler.ts +1 -1
- package/lib/browser/tests/heapMemoryProfiler.test.ts +64 -0
- package/lib/browser/tests/iframeHeapProfiler.test.ts +1 -1
- package/lib/index.ts +2 -0
- package/lib/runner/scenarioRunner.ts +9 -6
- package/lib/scenarios/index.ts +8 -6
- package/lib/scenarios/one-admin/index.ts +7 -1
- package/lib/scenarios/pipeline/index.ts +12 -0
- package/lib/scenarios/pipeline/page-models/PipelinePageModel.ts +46 -0
- package/lib/scenarios/pipeline/page-models/index.ts +1 -0
- package/lib/scenarios/pipeline/pipeline-task-navigation.scenario.ts +42 -0
- package/package.json +5 -4
- package/reports/analysis/index.html +1 -1
- package/reports/analysis/thresholdEvaluator.ts.html +1 -1
- package/reports/browser/heapMemoryProfiler.ts.html +937 -0
- package/reports/browser/iframeHeapProfiler.ts.html +5 -5
- package/reports/browser/index.html +25 -10
- package/reports/cli/commands/index.html +1 -1
- package/reports/cli/commands/listCommand.ts.html +1 -1
- package/reports/cli/commands/runCommand.ts.html +1 -1
- package/reports/cli/index.html +1 -1
- package/reports/cli/index.ts.html +1 -1
- package/reports/config/index.html +1 -1
- package/reports/config/missingRequiredParamError.ts.html +1 -1
- package/reports/config/requiredEnvParams.ts.html +1 -1
- package/reports/config/runnerConfigLoader.ts.html +1 -1
- package/reports/config/runnerConfigSchema.ts.html +1 -1
- package/reports/config/sources/cliOverrideConfigSource.ts.html +1 -1
- package/reports/config/sources/configSource.ts.html +1 -1
- package/reports/config/sources/envVarConfigSource.ts.html +1 -1
- package/reports/config/sources/fileConfigSource.ts.html +1 -1
- package/reports/config/sources/index.html +1 -1
- package/reports/config/unknownEnvError.ts.html +1 -1
- package/reports/index.html +63 -33
- package/reports/lcov-report/analysis/index.html +1 -1
- package/reports/lcov-report/analysis/thresholdEvaluator.ts.html +1 -1
- package/reports/lcov-report/browser/heapMemoryProfiler.ts.html +937 -0
- package/reports/lcov-report/browser/iframeHeapProfiler.ts.html +5 -5
- package/reports/lcov-report/browser/index.html +25 -10
- package/reports/lcov-report/cli/commands/index.html +1 -1
- package/reports/lcov-report/cli/commands/listCommand.ts.html +1 -1
- package/reports/lcov-report/cli/commands/runCommand.ts.html +1 -1
- package/reports/lcov-report/cli/index.html +1 -1
- package/reports/lcov-report/cli/index.ts.html +1 -1
- package/reports/lcov-report/config/index.html +1 -1
- package/reports/lcov-report/config/missingRequiredParamError.ts.html +1 -1
- package/reports/lcov-report/config/requiredEnvParams.ts.html +1 -1
- package/reports/lcov-report/config/runnerConfigLoader.ts.html +1 -1
- package/reports/lcov-report/config/runnerConfigSchema.ts.html +1 -1
- package/reports/lcov-report/config/sources/cliOverrideConfigSource.ts.html +1 -1
- package/reports/lcov-report/config/sources/configSource.ts.html +1 -1
- package/reports/lcov-report/config/sources/envVarConfigSource.ts.html +1 -1
- package/reports/lcov-report/config/sources/fileConfigSource.ts.html +1 -1
- package/reports/lcov-report/config/sources/index.html +1 -1
- package/reports/lcov-report/config/unknownEnvError.ts.html +1 -1
- package/reports/lcov-report/index.html +63 -33
- package/reports/lcov-report/registry/index.html +1 -1
- package/reports/lcov-report/registry/scenarioRegistry.ts.html +1 -1
- package/reports/lcov-report/reporting/consoleReporter.ts.html +1 -1
- package/reports/lcov-report/reporting/index.html +1 -1
- package/reports/lcov-report/reporting/junitReporter.ts.html +1 -1
- package/reports/lcov-report/runner/aiEnhancementStep.ts.html +1 -1
- package/reports/lcov-report/runner/batchRunner.ts.html +1 -1
- package/reports/lcov-report/runner/index.html +15 -15
- package/reports/lcov-report/runner/scenarioRunner.ts.html +23 -14
- package/reports/lcov-report/scenarios/index.html +8 -8
- package/reports/lcov-report/scenarios/index.ts.html +20 -14
- package/reports/lcov-report/scenarios/one-admin/export-navigation.scenario.ts.html +1 -1
- package/reports/lcov-report/scenarios/one-admin/index.html +5 -5
- package/reports/lcov-report/scenarios/one-admin/index.ts.html +24 -6
- package/reports/lcov-report/scenarios/one-admin/page-models/AdminLandingPageModel.ts.html +1 -1
- package/reports/lcov-report/scenarios/one-admin/page-models/ExportPageModel.ts.html +1 -1
- package/reports/lcov-report/scenarios/one-admin/page-models/SelectSettingsPageModel.ts.html +1 -1
- package/reports/lcov-report/scenarios/one-admin/page-models/index.html +1 -1
- package/reports/lcov-report/scenarios/pipeline/index.html +131 -0
- package/reports/lcov-report/scenarios/pipeline/index.ts.html +121 -0
- package/reports/lcov-report/scenarios/pipeline/page-models/PipelinePageModel.ts.html +223 -0
- package/reports/lcov-report/scenarios/pipeline/page-models/index.html +116 -0
- package/reports/lcov-report/scenarios/pipeline/pipeline-task-navigation.scenario.ts.html +211 -0
- package/reports/lcov-report/types/config.ts.html +1 -1
- package/reports/lcov-report/types/index.html +1 -1
- package/reports/lcov.info +225 -40
- package/reports/registry/index.html +1 -1
- package/reports/registry/scenarioRegistry.ts.html +1 -1
- package/reports/reporting/consoleReporter.ts.html +1 -1
- package/reports/reporting/index.html +1 -1
- package/reports/reporting/junitReporter.ts.html +1 -1
- package/reports/runner/aiEnhancementStep.ts.html +1 -1
- package/reports/runner/batchRunner.ts.html +1 -1
- package/reports/runner/index.html +15 -15
- package/reports/runner/scenarioRunner.ts.html +23 -14
- package/reports/scenarios/index.html +8 -8
- package/reports/scenarios/index.ts.html +20 -14
- package/reports/scenarios/one-admin/export-navigation.scenario.ts.html +1 -1
- package/reports/scenarios/one-admin/index.html +5 -5
- package/reports/scenarios/one-admin/index.ts.html +24 -6
- package/reports/scenarios/one-admin/page-models/AdminLandingPageModel.ts.html +1 -1
- package/reports/scenarios/one-admin/page-models/ExportPageModel.ts.html +1 -1
- package/reports/scenarios/one-admin/page-models/SelectSettingsPageModel.ts.html +1 -1
- package/reports/scenarios/one-admin/page-models/index.html +1 -1
- package/reports/scenarios/pipeline/index.html +131 -0
- package/reports/scenarios/pipeline/index.ts.html +121 -0
- package/reports/scenarios/pipeline/page-models/PipelinePageModel.ts.html +223 -0
- package/reports/scenarios/pipeline/page-models/index.html +116 -0
- package/reports/scenarios/pipeline/pipeline-task-navigation.scenario.ts.html +211 -0
- package/reports/types/config.ts.html +1 -1
- package/reports/types/index.html +1 -1
- package/test-report.xml +57 -53
package/dist/bin/leak-runner.js
CHANGED
|
@@ -4,15 +4,163 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// lib/runner/scenarioRunner.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import fs2 from "node:fs";
|
|
8
|
+
import path2 from "node:path";
|
|
9
9
|
import {
|
|
10
10
|
chromium
|
|
11
11
|
} from "@playwright/test";
|
|
12
12
|
import { AuthManager, PageSetup } from "@elliemae/smoked-suite";
|
|
13
13
|
|
|
14
|
+
// lib/browser/heapMemoryProfiler.ts
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import { HeapDoctor } from "@elliemae/encw-heap-doctor";
|
|
18
|
+
var HeapMemoryProfiler = class {
|
|
19
|
+
/**
|
|
20
|
+
* @param {Page} page - The Playwright `Page` instance for the current test.
|
|
21
|
+
* @param {string} outputDir - Directory where `.heapsnapshot` and `.md` files are written.
|
|
22
|
+
* Created automatically if it does not exist.
|
|
23
|
+
*/
|
|
24
|
+
constructor(page, outputDir) {
|
|
25
|
+
this.page = page;
|
|
26
|
+
this.outputDir = outputDir;
|
|
27
|
+
}
|
|
28
|
+
/** Memento store: maps snapshot label → absolute file path on disk. */
|
|
29
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
30
|
+
getCDPTarget() {
|
|
31
|
+
return this.page;
|
|
32
|
+
}
|
|
33
|
+
// ─── Browser guard ──────────────────────────────────────────────────────────
|
|
34
|
+
isChromium() {
|
|
35
|
+
return this.page.context().browser()?.browserType().name() === "chromium";
|
|
36
|
+
}
|
|
37
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Capture a Chrome heap snapshot via CDP and persist it to disk.
|
|
40
|
+
*
|
|
41
|
+
* The snapshot is streamed in chunks via `HeapProfiler.addHeapSnapshotChunk`,
|
|
42
|
+
* joined, and written as a single `.heapsnapshot` file. The file path is cached
|
|
43
|
+
* internally under `label` so {@link compare} can resolve it by name.
|
|
44
|
+
* @param {string} label - Logical name for this snapshot (e.g. `'before'`, `'after'`).
|
|
45
|
+
* Used as the filename prefix and as the Memento key.
|
|
46
|
+
* @returns {Promise<string>} Absolute path to the written file, or `''` on non-Chromium browsers.
|
|
47
|
+
*/
|
|
48
|
+
async captureSnapshot(label) {
|
|
49
|
+
if (!this.isChromium()) return "";
|
|
50
|
+
const client = await this.page.context().newCDPSession(this.getCDPTarget());
|
|
51
|
+
await client.send("HeapProfiler.enable");
|
|
52
|
+
fs.mkdirSync(this.outputDir, { recursive: true });
|
|
53
|
+
const filePath = path.join(
|
|
54
|
+
this.outputDir,
|
|
55
|
+
`${label}-${Date.now()}.heapsnapshot`
|
|
56
|
+
);
|
|
57
|
+
const writeStream = fs.createWriteStream(filePath);
|
|
58
|
+
client.on(
|
|
59
|
+
"HeapProfiler.addHeapSnapshotChunk",
|
|
60
|
+
({ chunk }) => {
|
|
61
|
+
writeStream.write(chunk);
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
await client.send("HeapProfiler.takeHeapSnapshot", {
|
|
65
|
+
reportProgress: false
|
|
66
|
+
});
|
|
67
|
+
await client.send("HeapProfiler.disable");
|
|
68
|
+
await client.detach();
|
|
69
|
+
await new Promise((resolve, reject) => {
|
|
70
|
+
writeStream.end((err) => {
|
|
71
|
+
if (err) reject(err);
|
|
72
|
+
else resolve();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
this.snapshots.set(label, filePath);
|
|
76
|
+
return filePath;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Compare two previously captured snapshots by their labels.
|
|
80
|
+
*
|
|
81
|
+
* Runs `HeapDoctor.compare()` on the resolved file paths and writes a
|
|
82
|
+
* markdown report to {@link outputDir}.
|
|
83
|
+
* @param {CompareParams} params - {@link CompareParams}
|
|
84
|
+
* @returns {Promise<ComparisonReport | null>} `ComparisonReport`, or `null` on non-Chromium browsers.
|
|
85
|
+
* @throws If either label has no cached snapshot path.
|
|
86
|
+
* @throws If `HeapDoctor.compare` returns `ok: false`.
|
|
87
|
+
*/
|
|
88
|
+
async compare(params) {
|
|
89
|
+
const { beforeLabel, afterLabel, topN, originAllowList } = params;
|
|
90
|
+
if (!this.isChromium()) return null;
|
|
91
|
+
const before = this.snapshots.get(beforeLabel);
|
|
92
|
+
const after = this.snapshots.get(afterLabel);
|
|
93
|
+
if (!before || !after) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`HeapMemoryProfiler: snapshot not found for labels "${beforeLabel}" / "${afterLabel}". Call captureSnapshot() before compare().`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const result = await new HeapDoctor({ topN, originAllowList }).compare(
|
|
99
|
+
before,
|
|
100
|
+
after
|
|
101
|
+
);
|
|
102
|
+
if (!result.ok) throw result.error;
|
|
103
|
+
fs.writeFileSync(
|
|
104
|
+
path.join(this.outputDir, `comparison-${Date.now()}.md`),
|
|
105
|
+
result.value.markdown
|
|
106
|
+
);
|
|
107
|
+
return result.value;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Orchestrate the full heap profiling sequence for a user flow.
|
|
111
|
+
*
|
|
112
|
+
* Sequence (Template Method):
|
|
113
|
+
* 1. Capture **before** snapshot
|
|
114
|
+
* 2. Execute `flow` exactly `heapIterations` times
|
|
115
|
+
* 3. Capture **after** snapshot
|
|
116
|
+
* 4. Run `HeapDoctor.compare` and write the markdown report
|
|
117
|
+
* 5. If `failOnLeak` is `true` and `retainedSizeDelta > leakThresholdBytes`, throw
|
|
118
|
+
*
|
|
119
|
+
* On non-Chromium browsers: `flow` still runs `heapIterations` times,
|
|
120
|
+
* no snapshots are taken, and `null` is returned.
|
|
121
|
+
* @param {() => Promise<void>} flow - Async function containing the user actions to profile.
|
|
122
|
+
* @param {HeapProfilingOptions} [options] - {@link HeapProfilingOptions}
|
|
123
|
+
* @returns {Promise<ComparisonReport | null>} `ComparisonReport` on Chromium, `null` on all other browsers.
|
|
124
|
+
*/
|
|
125
|
+
async withProfiling(flow, options = {}) {
|
|
126
|
+
const {
|
|
127
|
+
heapIterations = 1,
|
|
128
|
+
label = "flow",
|
|
129
|
+
topN,
|
|
130
|
+
failOnLeak = false,
|
|
131
|
+
leakThresholdBytes = 0,
|
|
132
|
+
originAllowList
|
|
133
|
+
} = options;
|
|
134
|
+
if (!this.isChromium()) {
|
|
135
|
+
for (let i = 0; i < heapIterations; i += 1) await flow();
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
await this.captureSnapshot(`${label}-before`);
|
|
139
|
+
for (let i = 0; i < heapIterations; i += 1) await flow();
|
|
140
|
+
await this.captureSnapshot(`${label}-after`);
|
|
141
|
+
const report = await this.compare({
|
|
142
|
+
beforeLabel: `${label}-before`,
|
|
143
|
+
afterLabel: `${label}-after`,
|
|
144
|
+
topN,
|
|
145
|
+
originAllowList
|
|
146
|
+
});
|
|
147
|
+
if (report && failOnLeak && report.delta.retainedSizeDelta > leakThresholdBytes) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`Memory leak detected: retained size grew by ${report.delta.retainedSizeDelta} bytes (threshold: ${leakThresholdBytes} bytes). New leak groups: ${report.delta.newLeakGroups.length}. See: ${this.outputDir}`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return report;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Clear the internal snapshot label cache.
|
|
156
|
+
* Call in `afterEach` to prevent snapshot paths bleeding across tests.
|
|
157
|
+
*/
|
|
158
|
+
clearSnapshots() {
|
|
159
|
+
this.snapshots.clear();
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
14
163
|
// lib/browser/iframeHeapProfiler.ts
|
|
15
|
-
import { HeapMemoryProfiler } from "@elliemae/smoked-suite";
|
|
16
164
|
var IframeHeapProfiler = class extends HeapMemoryProfiler {
|
|
17
165
|
constructor(profiledPage, frame, outputDir) {
|
|
18
166
|
super(profiledPage, outputDir);
|
|
@@ -104,8 +252,8 @@ async function forceGarbageCollection(page) {
|
|
|
104
252
|
await page.waitForTimeout(2e3);
|
|
105
253
|
}
|
|
106
254
|
function cleanupSnapshots(paths) {
|
|
107
|
-
if (paths.before &&
|
|
108
|
-
if (paths.after &&
|
|
255
|
+
if (paths.before && fs2.existsSync(paths.before)) fs2.unlinkSync(paths.before);
|
|
256
|
+
if (paths.after && fs2.existsSync(paths.after)) fs2.unlinkSync(paths.after);
|
|
109
257
|
}
|
|
110
258
|
var ScenarioRunner = class {
|
|
111
259
|
constructor(config) {
|
|
@@ -135,7 +283,7 @@ var ScenarioRunner = class {
|
|
|
135
283
|
return this.buildResult({ scenario, report, error, startTime });
|
|
136
284
|
}
|
|
137
285
|
async runScenarioInPage(page, scenario, paths) {
|
|
138
|
-
const snapshotsDir =
|
|
286
|
+
const snapshotsDir = path2.join(this.config.runner.outputDir, "snapshots");
|
|
139
287
|
const { profiler, frame } = await this.setupAndProfile(
|
|
140
288
|
page,
|
|
141
289
|
scenario,
|
|
@@ -146,11 +294,12 @@ var ScenarioRunner = class {
|
|
|
146
294
|
await this.repeatScenarioActions(scenario, page, frame);
|
|
147
295
|
await forceGarbageCollection(page);
|
|
148
296
|
paths.after = await profiler.captureSnapshot("after");
|
|
149
|
-
const report = await profiler.compare(
|
|
150
|
-
"before",
|
|
151
|
-
"after",
|
|
152
|
-
this.config.runner.topN
|
|
153
|
-
|
|
297
|
+
const report = await profiler.compare({
|
|
298
|
+
beforeLabel: "before",
|
|
299
|
+
afterLabel: "after",
|
|
300
|
+
topN: this.config.runner.topN,
|
|
301
|
+
originAllowList: [new URL(this.config.env.baseUrl).origin]
|
|
302
|
+
});
|
|
154
303
|
if (report) await this.writeReport(report, scenario);
|
|
155
304
|
return report;
|
|
156
305
|
}
|
|
@@ -166,13 +315,15 @@ var ScenarioRunner = class {
|
|
|
166
315
|
});
|
|
167
316
|
process.stderr.write(`[scenarioRunner] post-login URL = ${page.url()}
|
|
168
317
|
`);
|
|
169
|
-
|
|
318
|
+
if (!page.url().includes(scenario.url())) {
|
|
319
|
+
await page.goto(scenario.url(), { waitUntil: "load", timeout: 3e4 });
|
|
320
|
+
}
|
|
170
321
|
process.stderr.write(
|
|
171
322
|
`[scenarioRunner] settled URL before iframe = ${page.url()}
|
|
172
323
|
`
|
|
173
324
|
);
|
|
174
325
|
const frame = await resolveIframe(page, scenario.microappSelector);
|
|
175
|
-
|
|
326
|
+
fs2.mkdirSync(snapshotsDir, { recursive: true });
|
|
176
327
|
const profiler = new IframeHeapProfiler(page, frame, snapshotsDir);
|
|
177
328
|
return { profiler, frame };
|
|
178
329
|
}
|
|
@@ -186,17 +337,17 @@ var ScenarioRunner = class {
|
|
|
186
337
|
}
|
|
187
338
|
}
|
|
188
339
|
async writeReport(report, scenario) {
|
|
189
|
-
const reportPath =
|
|
340
|
+
const reportPath = path2.join(
|
|
190
341
|
this.config.runner.outputDir,
|
|
191
342
|
`${scenario.id}.md`
|
|
192
343
|
);
|
|
193
|
-
|
|
344
|
+
fs2.mkdirSync(path2.dirname(reportPath), { recursive: true });
|
|
194
345
|
const markdown = await enhanceMarkdownIfConfigured(
|
|
195
346
|
report,
|
|
196
347
|
this.config,
|
|
197
348
|
scenario.name
|
|
198
349
|
);
|
|
199
|
-
|
|
350
|
+
fs2.writeFileSync(reportPath, markdown);
|
|
200
351
|
}
|
|
201
352
|
buildResult(input) {
|
|
202
353
|
const { scenario, report, error, startTime } = input;
|
|
@@ -312,8 +463,8 @@ var ConsoleReporter = class {
|
|
|
312
463
|
};
|
|
313
464
|
|
|
314
465
|
// lib/reporting/junitReporter.ts
|
|
315
|
-
import
|
|
316
|
-
import
|
|
466
|
+
import fs3 from "node:fs";
|
|
467
|
+
import path3 from "node:path";
|
|
317
468
|
function escapeXml(str) {
|
|
318
469
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
319
470
|
}
|
|
@@ -349,8 +500,8 @@ var JunitReporter = class {
|
|
|
349
500
|
" </testsuite>",
|
|
350
501
|
"</testsuites>"
|
|
351
502
|
].join("\n");
|
|
352
|
-
|
|
353
|
-
|
|
503
|
+
fs3.mkdirSync(outputDir, { recursive: true });
|
|
504
|
+
fs3.writeFileSync(path3.join(outputDir, "test-results.xml"), xml, "utf8");
|
|
354
505
|
return Promise.resolve();
|
|
355
506
|
}
|
|
356
507
|
};
|
|
@@ -398,7 +549,7 @@ var EnvVarConfigSource = class {
|
|
|
398
549
|
};
|
|
399
550
|
|
|
400
551
|
// lib/config/sources/fileConfigSource.ts
|
|
401
|
-
import
|
|
552
|
+
import fs4 from "node:fs";
|
|
402
553
|
|
|
403
554
|
// lib/config/runnerConfigSchema.ts
|
|
404
555
|
import { z } from "zod";
|
|
@@ -428,8 +579,8 @@ var FileConfigSource = class {
|
|
|
428
579
|
priority = 1;
|
|
429
580
|
name = "file";
|
|
430
581
|
load() {
|
|
431
|
-
if (!
|
|
432
|
-
const raw =
|
|
582
|
+
if (!fs4.existsSync(this.filePath)) return {};
|
|
583
|
+
const raw = fs4.readFileSync(this.filePath, "utf8");
|
|
433
584
|
let parsed;
|
|
434
585
|
try {
|
|
435
586
|
parsed = JSON.parse(raw);
|
|
@@ -805,8 +956,8 @@ var exportNavigationScenario = {
|
|
|
805
956
|
await page.frameLocator(IFRAME_SELECTOR2).getByText("ONE ADMIN CONSOLE").waitFor({ state: "visible", timeout: 12e4 });
|
|
806
957
|
},
|
|
807
958
|
async action(page, frame) {
|
|
808
|
-
const
|
|
809
|
-
if (
|
|
959
|
+
const path4 = new URL(page.url()).pathname.replace(/\/$/, "");
|
|
960
|
+
if (path4 === "/admin") {
|
|
810
961
|
await page.frameLocator(IFRAME_SELECTOR2).getByText("ONE ADMIN CONSOLE").click();
|
|
811
962
|
}
|
|
812
963
|
const settings = new SelectSettingsPageModel(frame);
|
|
@@ -832,12 +983,90 @@ var exportNavigationScenario = {
|
|
|
832
983
|
var oneAdminScenarios = [
|
|
833
984
|
exportNavigationScenario
|
|
834
985
|
];
|
|
835
|
-
|
|
836
|
-
// lib/scenarios/index.ts
|
|
837
|
-
var scenarioRegistry = new ScenarioRegistry().register({
|
|
986
|
+
var oneAdminScenarioGroup = {
|
|
838
987
|
microapp: "one-admin",
|
|
839
988
|
scenarios: oneAdminScenarios
|
|
840
|
-
}
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
// lib/scenarios/pipeline/page-models/PipelinePageModel.ts
|
|
992
|
+
var PipelinePageModel = class _PipelinePageModel {
|
|
993
|
+
constructor(page) {
|
|
994
|
+
this.page = page;
|
|
995
|
+
}
|
|
996
|
+
static IFRAME_SELECTOR = '[data-testid="pui-iframe-container-pipelineui"]';
|
|
997
|
+
static TASKS_IFRAME_SELECTOR = '[data-testid="pui-iframe-container-taskspipeline"]';
|
|
998
|
+
static SPINNER_TEST_ID = "ds-circularindeterminateindicator-svg";
|
|
999
|
+
async waitForPipelineReady() {
|
|
1000
|
+
await this.#waitForSpinner(_PipelinePageModel.IFRAME_SELECTOR);
|
|
1001
|
+
}
|
|
1002
|
+
async clickTasksTab() {
|
|
1003
|
+
await this.page.getByRole("tab", { name: "Tasks" }).click();
|
|
1004
|
+
}
|
|
1005
|
+
async clickPipelineTab() {
|
|
1006
|
+
await this.page.getByRole("tab", { name: "Loans" }).click();
|
|
1007
|
+
}
|
|
1008
|
+
async waitForPipelineSpinner() {
|
|
1009
|
+
await this.#waitForSpinner(_PipelinePageModel.IFRAME_SELECTOR);
|
|
1010
|
+
}
|
|
1011
|
+
async waitForTasksReady() {
|
|
1012
|
+
await this.page.locator(_PipelinePageModel.TASKS_IFRAME_SELECTOR).contentFrame().getByTestId("circular-indicator").waitFor({ state: "hidden" });
|
|
1013
|
+
}
|
|
1014
|
+
async #waitForSpinner(iframeSelector) {
|
|
1015
|
+
const spinner = this.page.frameLocator(iframeSelector).getByTestId(_PipelinePageModel.SPINNER_TEST_ID);
|
|
1016
|
+
await spinner.waitFor({ state: "visible" });
|
|
1017
|
+
await spinner.waitFor({ state: "hidden" });
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
// lib/scenarios/pipeline/pipeline-task-navigation.scenario.ts
|
|
1022
|
+
var pipelineTaskNavigationScenario = {
|
|
1023
|
+
id: "pipeline-task-navigation",
|
|
1024
|
+
name: "Pipeline Task Navigation",
|
|
1025
|
+
description: "Navigate to pipeline task details and come back - verify iframe GC",
|
|
1026
|
+
tags: ["critical"],
|
|
1027
|
+
microappSelector: PipelinePageModel.IFRAME_SELECTOR,
|
|
1028
|
+
url: () => "/pipeline",
|
|
1029
|
+
async setup(page) {
|
|
1030
|
+
await AdminLandingPageModel.acceptCookiesIfShown(page);
|
|
1031
|
+
const pipeline = new PipelinePageModel(page);
|
|
1032
|
+
await pipeline.waitForPipelineReady();
|
|
1033
|
+
},
|
|
1034
|
+
async action(page) {
|
|
1035
|
+
const pipeline = new PipelinePageModel(page);
|
|
1036
|
+
const path4 = new URL(page.url()).pathname.replace(/\/$/, "");
|
|
1037
|
+
if (path4 === "/pipeline") {
|
|
1038
|
+
await pipeline.clickTasksTab();
|
|
1039
|
+
}
|
|
1040
|
+
await pipeline.waitForTasksReady();
|
|
1041
|
+
},
|
|
1042
|
+
async back(page) {
|
|
1043
|
+
const pipeline = new PipelinePageModel(page);
|
|
1044
|
+
await pipeline.clickPipelineTab();
|
|
1045
|
+
await pipeline.waitForPipelineSpinner();
|
|
1046
|
+
},
|
|
1047
|
+
repeat: () => 1,
|
|
1048
|
+
thresholds: {
|
|
1049
|
+
maxRetainedSizeDeltaBytes: 10 * 1024 * 1024,
|
|
1050
|
+
maxNewLeakGroups: 0
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
// lib/scenarios/pipeline/index.ts
|
|
1055
|
+
var pipelineScenarios = [
|
|
1056
|
+
pipelineTaskNavigationScenario
|
|
1057
|
+
];
|
|
1058
|
+
var pipelineScenarioGroup = {
|
|
1059
|
+
microapp: "pipeline",
|
|
1060
|
+
scenarios: pipelineScenarios
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
// lib/scenarios/index.ts
|
|
1064
|
+
var scenarioRegistry = (() => {
|
|
1065
|
+
const registry = new ScenarioRegistry();
|
|
1066
|
+
registry.register(oneAdminScenarioGroup);
|
|
1067
|
+
registry.register(pipelineScenarioGroup);
|
|
1068
|
+
return registry;
|
|
1069
|
+
})();
|
|
841
1070
|
|
|
842
1071
|
// lib/cli/index.ts
|
|
843
1072
|
function buildProgram(deps = defaultDeps()) {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var heapMemoryProfiler_exports = {};
|
|
30
|
+
__export(heapMemoryProfiler_exports, {
|
|
31
|
+
HeapMemoryProfiler: () => HeapMemoryProfiler
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(heapMemoryProfiler_exports);
|
|
34
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
35
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
36
|
+
var import_encw_heap_doctor = require("@elliemae/encw-heap-doctor");
|
|
37
|
+
class HeapMemoryProfiler {
|
|
38
|
+
/**
|
|
39
|
+
* @param {Page} page - The Playwright `Page` instance for the current test.
|
|
40
|
+
* @param {string} outputDir - Directory where `.heapsnapshot` and `.md` files are written.
|
|
41
|
+
* Created automatically if it does not exist.
|
|
42
|
+
*/
|
|
43
|
+
constructor(page, outputDir) {
|
|
44
|
+
this.page = page;
|
|
45
|
+
this.outputDir = outputDir;
|
|
46
|
+
}
|
|
47
|
+
page;
|
|
48
|
+
outputDir;
|
|
49
|
+
/** Memento store: maps snapshot label → absolute file path on disk. */
|
|
50
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
51
|
+
getCDPTarget() {
|
|
52
|
+
return this.page;
|
|
53
|
+
}
|
|
54
|
+
// ─── Browser guard ──────────────────────────────────────────────────────────
|
|
55
|
+
isChromium() {
|
|
56
|
+
return this.page.context().browser()?.browserType().name() === "chromium";
|
|
57
|
+
}
|
|
58
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Capture a Chrome heap snapshot via CDP and persist it to disk.
|
|
61
|
+
*
|
|
62
|
+
* The snapshot is streamed in chunks via `HeapProfiler.addHeapSnapshotChunk`,
|
|
63
|
+
* joined, and written as a single `.heapsnapshot` file. The file path is cached
|
|
64
|
+
* internally under `label` so {@link compare} can resolve it by name.
|
|
65
|
+
* @param {string} label - Logical name for this snapshot (e.g. `'before'`, `'after'`).
|
|
66
|
+
* Used as the filename prefix and as the Memento key.
|
|
67
|
+
* @returns {Promise<string>} Absolute path to the written file, or `''` on non-Chromium browsers.
|
|
68
|
+
*/
|
|
69
|
+
async captureSnapshot(label) {
|
|
70
|
+
if (!this.isChromium()) return "";
|
|
71
|
+
const client = await this.page.context().newCDPSession(this.getCDPTarget());
|
|
72
|
+
await client.send("HeapProfiler.enable");
|
|
73
|
+
import_node_fs.default.mkdirSync(this.outputDir, { recursive: true });
|
|
74
|
+
const filePath = import_node_path.default.join(
|
|
75
|
+
this.outputDir,
|
|
76
|
+
`${label}-${Date.now()}.heapsnapshot`
|
|
77
|
+
);
|
|
78
|
+
const writeStream = import_node_fs.default.createWriteStream(filePath);
|
|
79
|
+
client.on(
|
|
80
|
+
"HeapProfiler.addHeapSnapshotChunk",
|
|
81
|
+
({ chunk }) => {
|
|
82
|
+
writeStream.write(chunk);
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
await client.send("HeapProfiler.takeHeapSnapshot", {
|
|
86
|
+
reportProgress: false
|
|
87
|
+
});
|
|
88
|
+
await client.send("HeapProfiler.disable");
|
|
89
|
+
await client.detach();
|
|
90
|
+
await new Promise((resolve, reject) => {
|
|
91
|
+
writeStream.end((err) => {
|
|
92
|
+
if (err) reject(err);
|
|
93
|
+
else resolve();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
this.snapshots.set(label, filePath);
|
|
97
|
+
return filePath;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Compare two previously captured snapshots by their labels.
|
|
101
|
+
*
|
|
102
|
+
* Runs `HeapDoctor.compare()` on the resolved file paths and writes a
|
|
103
|
+
* markdown report to {@link outputDir}.
|
|
104
|
+
* @param {CompareParams} params - {@link CompareParams}
|
|
105
|
+
* @returns {Promise<ComparisonReport | null>} `ComparisonReport`, or `null` on non-Chromium browsers.
|
|
106
|
+
* @throws If either label has no cached snapshot path.
|
|
107
|
+
* @throws If `HeapDoctor.compare` returns `ok: false`.
|
|
108
|
+
*/
|
|
109
|
+
async compare(params) {
|
|
110
|
+
const { beforeLabel, afterLabel, topN, originAllowList } = params;
|
|
111
|
+
if (!this.isChromium()) return null;
|
|
112
|
+
const before = this.snapshots.get(beforeLabel);
|
|
113
|
+
const after = this.snapshots.get(afterLabel);
|
|
114
|
+
if (!before || !after) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`HeapMemoryProfiler: snapshot not found for labels "${beforeLabel}" / "${afterLabel}". Call captureSnapshot() before compare().`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const result = await new import_encw_heap_doctor.HeapDoctor({ topN, originAllowList }).compare(
|
|
120
|
+
before,
|
|
121
|
+
after
|
|
122
|
+
);
|
|
123
|
+
if (!result.ok) throw result.error;
|
|
124
|
+
import_node_fs.default.writeFileSync(
|
|
125
|
+
import_node_path.default.join(this.outputDir, `comparison-${Date.now()}.md`),
|
|
126
|
+
result.value.markdown
|
|
127
|
+
);
|
|
128
|
+
return result.value;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Orchestrate the full heap profiling sequence for a user flow.
|
|
132
|
+
*
|
|
133
|
+
* Sequence (Template Method):
|
|
134
|
+
* 1. Capture **before** snapshot
|
|
135
|
+
* 2. Execute `flow` exactly `heapIterations` times
|
|
136
|
+
* 3. Capture **after** snapshot
|
|
137
|
+
* 4. Run `HeapDoctor.compare` and write the markdown report
|
|
138
|
+
* 5. If `failOnLeak` is `true` and `retainedSizeDelta > leakThresholdBytes`, throw
|
|
139
|
+
*
|
|
140
|
+
* On non-Chromium browsers: `flow` still runs `heapIterations` times,
|
|
141
|
+
* no snapshots are taken, and `null` is returned.
|
|
142
|
+
* @param {() => Promise<void>} flow - Async function containing the user actions to profile.
|
|
143
|
+
* @param {HeapProfilingOptions} [options] - {@link HeapProfilingOptions}
|
|
144
|
+
* @returns {Promise<ComparisonReport | null>} `ComparisonReport` on Chromium, `null` on all other browsers.
|
|
145
|
+
*/
|
|
146
|
+
async withProfiling(flow, options = {}) {
|
|
147
|
+
const {
|
|
148
|
+
heapIterations = 1,
|
|
149
|
+
label = "flow",
|
|
150
|
+
topN,
|
|
151
|
+
failOnLeak = false,
|
|
152
|
+
leakThresholdBytes = 0,
|
|
153
|
+
originAllowList
|
|
154
|
+
} = options;
|
|
155
|
+
if (!this.isChromium()) {
|
|
156
|
+
for (let i = 0; i < heapIterations; i += 1) await flow();
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
await this.captureSnapshot(`${label}-before`);
|
|
160
|
+
for (let i = 0; i < heapIterations; i += 1) await flow();
|
|
161
|
+
await this.captureSnapshot(`${label}-after`);
|
|
162
|
+
const report = await this.compare({
|
|
163
|
+
beforeLabel: `${label}-before`,
|
|
164
|
+
afterLabel: `${label}-after`,
|
|
165
|
+
topN,
|
|
166
|
+
originAllowList
|
|
167
|
+
});
|
|
168
|
+
if (report && failOnLeak && report.delta.retainedSizeDelta > leakThresholdBytes) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Memory leak detected: retained size grew by ${report.delta.retainedSizeDelta} bytes (threshold: ${leakThresholdBytes} bytes). New leak groups: ${report.delta.newLeakGroups.length}. See: ${this.outputDir}`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return report;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Clear the internal snapshot label cache.
|
|
177
|
+
* Call in `afterEach` to prevent snapshot paths bleeding across tests.
|
|
178
|
+
*/
|
|
179
|
+
clearSnapshots() {
|
|
180
|
+
this.snapshots.clear();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -21,8 +21,8 @@ __export(iframeHeapProfiler_exports, {
|
|
|
21
21
|
IframeHeapProfiler: () => IframeHeapProfiler
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(iframeHeapProfiler_exports);
|
|
24
|
-
var
|
|
25
|
-
class IframeHeapProfiler extends
|
|
24
|
+
var import_heapMemoryProfiler = require("./heapMemoryProfiler.js");
|
|
25
|
+
class IframeHeapProfiler extends import_heapMemoryProfiler.HeapMemoryProfiler {
|
|
26
26
|
constructor(profiledPage, frame, outputDir) {
|
|
27
27
|
super(profiledPage, outputDir);
|
|
28
28
|
this.profiledPage = profiledPage;
|
package/dist/cjs/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
DEFAULT_THRESHOLDS: () => import_config.DEFAULT_THRESHOLDS,
|
|
25
25
|
EnvVarConfigSource: () => import_envVarConfigSource.EnvVarConfigSource,
|
|
26
26
|
FileConfigSource: () => import_fileConfigSource.FileConfigSource,
|
|
27
|
+
HeapMemoryProfiler: () => import_heapMemoryProfiler.HeapMemoryProfiler,
|
|
27
28
|
IframeHeapProfiler: () => import_iframeHeapProfiler.IframeHeapProfiler,
|
|
28
29
|
JunitReporter: () => import_junitReporter.JunitReporter,
|
|
29
30
|
MissingRequiredParamError: () => import_requiredEnvParams.MissingRequiredParamError,
|
|
@@ -41,6 +42,7 @@ var import_config = require("./types/config.js");
|
|
|
41
42
|
var import_scenarioRegistry = require("./registry/scenarioRegistry.js");
|
|
42
43
|
var import_batchRunner = require("./runner/batchRunner.js");
|
|
43
44
|
var import_scenarioRunner = require("./runner/scenarioRunner.js");
|
|
45
|
+
var import_heapMemoryProfiler = require("./browser/heapMemoryProfiler.js");
|
|
44
46
|
var import_iframeHeapProfiler = require("./browser/iframeHeapProfiler.js");
|
|
45
47
|
var import_thresholdEvaluator = require("./analysis/thresholdEvaluator.js");
|
|
46
48
|
var import_consoleReporter = require("./reporting/consoleReporter.js");
|
|
@@ -99,11 +99,12 @@ class ScenarioRunner {
|
|
|
99
99
|
await this.repeatScenarioActions(scenario, page, frame);
|
|
100
100
|
await forceGarbageCollection(page);
|
|
101
101
|
paths.after = await profiler.captureSnapshot("after");
|
|
102
|
-
const report = await profiler.compare(
|
|
103
|
-
"before",
|
|
104
|
-
"after",
|
|
105
|
-
this.config.runner.topN
|
|
106
|
-
|
|
102
|
+
const report = await profiler.compare({
|
|
103
|
+
beforeLabel: "before",
|
|
104
|
+
afterLabel: "after",
|
|
105
|
+
topN: this.config.runner.topN,
|
|
106
|
+
originAllowList: [new URL(this.config.env.baseUrl).origin]
|
|
107
|
+
});
|
|
107
108
|
if (report) await this.writeReport(report, scenario);
|
|
108
109
|
return report;
|
|
109
110
|
}
|
|
@@ -119,7 +120,9 @@ class ScenarioRunner {
|
|
|
119
120
|
});
|
|
120
121
|
process.stderr.write(`[scenarioRunner] post-login URL = ${page.url()}
|
|
121
122
|
`);
|
|
122
|
-
|
|
123
|
+
if (!page.url().includes(scenario.url())) {
|
|
124
|
+
await page.goto(scenario.url(), { waitUntil: "load", timeout: 3e4 });
|
|
125
|
+
}
|
|
123
126
|
process.stderr.write(
|
|
124
127
|
`[scenarioRunner] settled URL before iframe = ${page.url()}
|
|
125
128
|
`
|
|
@@ -23,7 +23,10 @@ __export(scenarios_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(scenarios_exports);
|
|
24
24
|
var import_scenarioRegistry = require("../registry/scenarioRegistry.js");
|
|
25
25
|
var import_one_admin = require("./one-admin/index.js");
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
var import_pipeline = require("./pipeline/index.js");
|
|
27
|
+
const scenarioRegistry = (() => {
|
|
28
|
+
const registry = new import_scenarioRegistry.ScenarioRegistry();
|
|
29
|
+
registry.register(import_one_admin.oneAdminScenarioGroup);
|
|
30
|
+
registry.register(import_pipeline.pipelineScenarioGroup);
|
|
31
|
+
return registry;
|
|
32
|
+
})();
|
|
@@ -18,10 +18,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var one_admin_exports = {};
|
|
20
20
|
__export(one_admin_exports, {
|
|
21
|
-
|
|
21
|
+
oneAdminScenarioGroup: () => oneAdminScenarioGroup
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(one_admin_exports);
|
|
24
24
|
var import_export_navigation_scenario = require("./export-navigation.scenario.js");
|
|
25
25
|
const oneAdminScenarios = [
|
|
26
26
|
import_export_navigation_scenario.exportNavigationScenario
|
|
27
27
|
];
|
|
28
|
+
const oneAdminScenarioGroup = {
|
|
29
|
+
microapp: "one-admin",
|
|
30
|
+
scenarios: oneAdminScenarios
|
|
31
|
+
};
|