@elliemae/encw-leak-runner 1.0.2
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/.eslintrc.cjs +10 -0
- package/.stylelintignore +4 -0
- package/CHANGELOG.md +51 -0
- package/README.md +309 -0
- package/babel.config.cjs +2 -0
- package/bin/leak-runner.ts +9 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/bin/leak-runner.js +792 -0
- package/dist/cjs/analysis/thresholdEvaluator.js +46 -0
- package/dist/cjs/browser/iframeHeapProfiler.js +46 -0
- package/dist/cjs/cli/command.js +16 -0
- package/dist/cjs/cli/commands/listCommand.js +47 -0
- package/dist/cjs/cli/commands/runCommand.js +111 -0
- package/dist/cjs/cli/index.js +42 -0
- package/dist/cjs/config/missingRequiredParamError.js +34 -0
- package/dist/cjs/config/requiredEnvParams.js +57 -0
- package/dist/cjs/config/runnerConfigLoader.js +73 -0
- package/dist/cjs/config/runnerConfigSchema.js +40 -0
- package/dist/cjs/config/sources/cliOverrideConfigSource.js +44 -0
- package/dist/cjs/config/sources/configSource.js +35 -0
- package/dist/cjs/config/sources/envVarConfigSource.js +41 -0
- package/dist/cjs/config/sources/fileConfigSource.js +62 -0
- package/dist/cjs/index.js +52 -0
- package/dist/cjs/package.json +7 -0
- package/dist/cjs/registry/scenarioRegistry.js +51 -0
- package/dist/cjs/reporting/consoleReporter.js +60 -0
- package/dist/cjs/reporting/junitReporter.js +75 -0
- package/dist/cjs/reporting/reporter.js +16 -0
- package/dist/cjs/runner/aiEnhancementStep.js +39 -0
- package/dist/cjs/runner/batchRunner.js +76 -0
- package/dist/cjs/runner/scenarioRunner.js +165 -0
- package/dist/cjs/scenarios/index.js +29 -0
- package/dist/cjs/scenarios/one-admin/export-navigation.scenario.js +50 -0
- package/dist/cjs/scenarios/one-admin/index.js +27 -0
- package/dist/cjs/scenarios/one-admin/page-models/ExportPageModel.js +43 -0
- package/dist/cjs/scenarios/one-admin/page-models/SelectSettingsPageModel.js +47 -0
- package/dist/cjs/scenarios/one-admin/page-models/index.js +26 -0
- package/dist/cjs/types/config.js +27 -0
- package/dist/cjs/types/results.js +16 -0
- package/dist/cjs/types/scenario.js +16 -0
- package/dist/esm/analysis/thresholdEvaluator.js +26 -0
- package/dist/esm/browser/iframeHeapProfiler.js +26 -0
- package/dist/esm/cli/command.js +0 -0
- package/dist/esm/cli/commands/listCommand.js +27 -0
- package/dist/esm/cli/commands/runCommand.js +93 -0
- package/dist/esm/cli/index.js +22 -0
- package/dist/esm/config/missingRequiredParamError.js +14 -0
- package/dist/esm/config/requiredEnvParams.js +37 -0
- package/dist/esm/config/runnerConfigLoader.js +53 -0
- package/dist/esm/config/runnerConfigSchema.js +20 -0
- package/dist/esm/config/sources/cliOverrideConfigSource.js +24 -0
- package/dist/esm/config/sources/configSource.js +15 -0
- package/dist/esm/config/sources/envVarConfigSource.js +21 -0
- package/dist/esm/config/sources/fileConfigSource.js +34 -0
- package/dist/esm/index.js +35 -0
- package/dist/esm/package.json +7 -0
- package/dist/esm/registry/scenarioRegistry.js +31 -0
- package/dist/esm/reporting/consoleReporter.js +40 -0
- package/dist/esm/reporting/junitReporter.js +45 -0
- package/dist/esm/reporting/reporter.js +0 -0
- package/dist/esm/runner/aiEnhancementStep.js +22 -0
- package/dist/esm/runner/batchRunner.js +56 -0
- package/dist/esm/runner/scenarioRunner.js +137 -0
- package/dist/esm/scenarios/index.js +9 -0
- package/dist/esm/scenarios/one-admin/export-navigation.scenario.js +33 -0
- package/dist/esm/scenarios/one-admin/index.js +7 -0
- package/dist/esm/scenarios/one-admin/page-models/ExportPageModel.js +23 -0
- package/dist/esm/scenarios/one-admin/page-models/SelectSettingsPageModel.js +27 -0
- package/dist/esm/scenarios/one-admin/page-models/index.js +6 -0
- package/dist/esm/types/config.js +7 -0
- package/dist/esm/types/results.js +0 -0
- package/dist/esm/types/scenario.js +0 -0
- package/dist/types/bin/leak-runner.d.ts +2 -0
- package/dist/types/lib/analysis/tests/thresholdEvaluator.test.d.ts +1 -0
- package/dist/types/lib/analysis/thresholdEvaluator.d.ts +6 -0
- package/dist/types/lib/browser/iframeHeapProfiler.d.ts +9 -0
- package/dist/types/lib/browser/tests/iframeHeapProfiler.test.d.ts +1 -0
- package/dist/types/lib/cli/command.d.ts +17 -0
- package/dist/types/lib/cli/commands/listCommand.d.ts +5 -0
- package/dist/types/lib/cli/commands/runCommand.d.ts +7 -0
- package/dist/types/lib/cli/index.d.ts +4 -0
- package/dist/types/lib/config/missingRequiredParamError.d.ts +4 -0
- package/dist/types/lib/config/requiredEnvParams.d.ts +16 -0
- package/dist/types/lib/config/runnerConfigLoader.d.ts +13 -0
- package/dist/types/lib/config/runnerConfigSchema.d.ts +78 -0
- package/dist/types/lib/config/sources/cliOverrideConfigSource.d.ts +14 -0
- package/dist/types/lib/config/sources/configSource.d.ts +14 -0
- package/dist/types/lib/config/sources/envVarConfigSource.d.ts +7 -0
- package/dist/types/lib/config/sources/fileConfigSource.d.ts +9 -0
- package/dist/types/lib/config/tests/cliOverrideConfigSource.test.d.ts +1 -0
- package/dist/types/lib/config/tests/envVarConfigSource.test.d.ts +1 -0
- package/dist/types/lib/config/tests/fileConfigSource.test.d.ts +1 -0
- package/dist/types/lib/config/tests/requiredEnvParams.test.d.ts +1 -0
- package/dist/types/lib/config/tests/runnerConfigLoader.test.d.ts +1 -0
- package/dist/types/lib/index.d.ts +18 -0
- package/dist/types/lib/registry/scenarioRegistry.d.ts +18 -0
- package/dist/types/lib/registry/tests/scenarioRegistry.test.d.ts +1 -0
- package/dist/types/lib/reporting/consoleReporter.d.ts +5 -0
- package/dist/types/lib/reporting/junitReporter.d.ts +5 -0
- package/dist/types/lib/reporting/reporter.d.ts +4 -0
- package/dist/types/lib/reporting/tests/consoleReporter.test.d.ts +1 -0
- package/dist/types/lib/reporting/tests/junitReporter.test.d.ts +1 -0
- package/dist/types/lib/runner/aiEnhancementStep.d.ts +15 -0
- package/dist/types/lib/runner/batchRunner.d.ts +14 -0
- package/dist/types/lib/runner/scenarioRunner.d.ts +15 -0
- package/dist/types/lib/runner/tests/aiEnhancementStep.test.d.ts +1 -0
- package/dist/types/lib/runner/tests/batchRunner.test.d.ts +1 -0
- package/dist/types/lib/runner/tests/scenarioRunner.test.d.ts +1 -0
- package/dist/types/lib/scenarios/index.d.ts +2 -0
- package/dist/types/lib/scenarios/one-admin/export-navigation.scenario.d.ts +2 -0
- package/dist/types/lib/scenarios/one-admin/index.d.ts +2 -0
- package/dist/types/lib/scenarios/one-admin/page-models/ExportPageModel.d.ts +8 -0
- package/dist/types/lib/scenarios/one-admin/page-models/SelectSettingsPageModel.d.ts +10 -0
- package/dist/types/lib/scenarios/one-admin/page-models/index.d.ts +2 -0
- package/dist/types/lib/types/config.d.ts +26 -0
- package/dist/types/lib/types/results.d.ts +19 -0
- package/dist/types/lib/types/scenario.d.ts +17 -0
- package/jest.config.cjs +9 -0
- package/leak-runner.config.json +13 -0
- package/leak-runner.schema.json +27 -0
- package/lib/analysis/tests/thresholdEvaluator.test.ts +125 -0
- package/lib/analysis/thresholdEvaluator.ts +36 -0
- package/lib/browser/iframeHeapProfiler.ts +30 -0
- package/lib/browser/tests/iframeHeapProfiler.test.ts +71 -0
- package/lib/cli/command.ts +19 -0
- package/lib/cli/commands/listCommand.ts +36 -0
- package/lib/cli/commands/runCommand.ts +126 -0
- package/lib/cli/index.ts +25 -0
- package/lib/config/missingRequiredParamError.ts +10 -0
- package/lib/config/requiredEnvParams.ts +50 -0
- package/lib/config/runnerConfigLoader.ts +84 -0
- package/lib/config/runnerConfigSchema.ts +27 -0
- package/lib/config/sources/cliOverrideConfigSource.ts +30 -0
- package/lib/config/sources/configSource.ts +27 -0
- package/lib/config/sources/envVarConfigSource.ts +23 -0
- package/lib/config/sources/fileConfigSource.ts +39 -0
- package/lib/config/tests/cliOverrideConfigSource.test.ts +25 -0
- package/lib/config/tests/envVarConfigSource.test.ts +57 -0
- package/lib/config/tests/fileConfigSource.test.ts +49 -0
- package/lib/config/tests/requiredEnvParams.test.ts +113 -0
- package/lib/config/tests/runnerConfigLoader.test.ts +59 -0
- package/lib/index.ts +37 -0
- package/lib/registry/scenarioRegistry.ts +48 -0
- package/lib/registry/tests/scenarioRegistry.test.ts +96 -0
- package/lib/reporting/consoleReporter.ts +48 -0
- package/lib/reporting/junitReporter.ts +62 -0
- package/lib/reporting/reporter.ts +5 -0
- package/lib/reporting/tests/consoleReporter.test.ts +82 -0
- package/lib/reporting/tests/junitReporter.test.ts +103 -0
- package/lib/runner/aiEnhancementStep.ts +39 -0
- package/lib/runner/batchRunner.ts +71 -0
- package/lib/runner/scenarioRunner.ts +189 -0
- package/lib/runner/tests/aiEnhancementStep.test.ts +174 -0
- package/lib/runner/tests/batchRunner.test.ts +133 -0
- package/lib/runner/tests/scenarioRunner.test.ts +162 -0
- package/lib/scenarios/index.ts +8 -0
- package/lib/scenarios/one-admin/export-navigation.scenario.ts +38 -0
- package/lib/scenarios/one-admin/index.ts +6 -0
- package/lib/scenarios/one-admin/page-models/ExportPageModel.ts +26 -0
- package/lib/scenarios/one-admin/page-models/SelectSettingsPageModel.ts +30 -0
- package/lib/scenarios/one-admin/page-models/index.ts +2 -0
- package/lib/types/config.ts +34 -0
- package/lib/types/results.ts +22 -0
- package/lib/types/scenario.ts +18 -0
- package/package.json +46 -0
- package/reports/analysis/index.html +116 -0
- package/reports/analysis/thresholdEvaluator.ts.html +193 -0
- package/reports/base.css +224 -0
- package/reports/block-navigation.js +87 -0
- package/reports/browser/iframeHeapProfiler.ts.html +175 -0
- package/reports/browser/index.html +116 -0
- package/reports/cli/commands/index.html +131 -0
- package/reports/cli/commands/listCommand.ts.html +193 -0
- package/reports/cli/commands/runCommand.ts.html +463 -0
- package/reports/cli/index.html +116 -0
- package/reports/cli/index.ts.html +160 -0
- package/reports/config/index.html +161 -0
- package/reports/config/missingRequiredParamError.ts.html +115 -0
- package/reports/config/requiredEnvParams.ts.html +235 -0
- package/reports/config/runnerConfigLoader.ts.html +337 -0
- package/reports/config/runnerConfigSchema.ts.html +166 -0
- package/reports/config/sources/cliOverrideConfigSource.ts.html +175 -0
- package/reports/config/sources/configSource.ts.html +166 -0
- package/reports/config/sources/envVarConfigSource.ts.html +154 -0
- package/reports/config/sources/fileConfigSource.ts.html +202 -0
- package/reports/config/sources/index.html +161 -0
- package/reports/favicon.png +0 -0
- package/reports/index.html +296 -0
- package/reports/lcov-report/analysis/index.html +116 -0
- package/reports/lcov-report/analysis/thresholdEvaluator.ts.html +193 -0
- package/reports/lcov-report/base.css +224 -0
- package/reports/lcov-report/block-navigation.js +87 -0
- package/reports/lcov-report/browser/iframeHeapProfiler.ts.html +175 -0
- package/reports/lcov-report/browser/index.html +116 -0
- package/reports/lcov-report/cli/commands/index.html +131 -0
- package/reports/lcov-report/cli/commands/listCommand.ts.html +193 -0
- package/reports/lcov-report/cli/commands/runCommand.ts.html +463 -0
- package/reports/lcov-report/cli/index.html +116 -0
- package/reports/lcov-report/cli/index.ts.html +160 -0
- package/reports/lcov-report/config/index.html +161 -0
- package/reports/lcov-report/config/missingRequiredParamError.ts.html +115 -0
- package/reports/lcov-report/config/requiredEnvParams.ts.html +235 -0
- package/reports/lcov-report/config/runnerConfigLoader.ts.html +337 -0
- package/reports/lcov-report/config/runnerConfigSchema.ts.html +166 -0
- package/reports/lcov-report/config/sources/cliOverrideConfigSource.ts.html +175 -0
- package/reports/lcov-report/config/sources/configSource.ts.html +166 -0
- package/reports/lcov-report/config/sources/envVarConfigSource.ts.html +154 -0
- package/reports/lcov-report/config/sources/fileConfigSource.ts.html +202 -0
- package/reports/lcov-report/config/sources/index.html +161 -0
- package/reports/lcov-report/favicon.png +0 -0
- package/reports/lcov-report/index.html +296 -0
- package/reports/lcov-report/prettify.css +1 -0
- package/reports/lcov-report/prettify.js +2 -0
- package/reports/lcov-report/registry/index.html +116 -0
- package/reports/lcov-report/registry/scenarioRegistry.ts.html +229 -0
- package/reports/lcov-report/reporting/consoleReporter.ts.html +229 -0
- package/reports/lcov-report/reporting/index.html +131 -0
- package/reports/lcov-report/reporting/junitReporter.ts.html +271 -0
- package/reports/lcov-report/runner/aiEnhancementStep.ts.html +202 -0
- package/reports/lcov-report/runner/batchRunner.ts.html +298 -0
- package/reports/lcov-report/runner/index.html +146 -0
- package/reports/lcov-report/runner/scenarioRunner.ts.html +652 -0
- package/reports/lcov-report/scenarios/index.html +116 -0
- package/reports/lcov-report/scenarios/index.ts.html +109 -0
- package/reports/lcov-report/scenarios/one-admin/export-navigation.scenario.ts.html +199 -0
- package/reports/lcov-report/scenarios/one-admin/index.html +131 -0
- package/reports/lcov-report/scenarios/one-admin/index.ts.html +103 -0
- package/reports/lcov-report/scenarios/one-admin/page-models/ExportPageModel.ts.html +163 -0
- package/reports/lcov-report/scenarios/one-admin/page-models/SelectSettingsPageModel.ts.html +175 -0
- package/reports/lcov-report/scenarios/one-admin/page-models/index.html +131 -0
- package/reports/lcov-report/sort-arrow-sprite.png +0 -0
- package/reports/lcov-report/sorter.js +210 -0
- package/reports/lcov-report/types/config.ts.html +187 -0
- package/reports/lcov-report/types/index.html +116 -0
- package/reports/lcov.info +883 -0
- package/reports/prettify.css +1 -0
- package/reports/prettify.js +2 -0
- package/reports/registry/index.html +116 -0
- package/reports/registry/scenarioRegistry.ts.html +229 -0
- package/reports/reporting/consoleReporter.ts.html +229 -0
- package/reports/reporting/index.html +131 -0
- package/reports/reporting/junitReporter.ts.html +271 -0
- package/reports/runner/aiEnhancementStep.ts.html +202 -0
- package/reports/runner/batchRunner.ts.html +298 -0
- package/reports/runner/index.html +146 -0
- package/reports/runner/scenarioRunner.ts.html +652 -0
- package/reports/scenarios/index.html +116 -0
- package/reports/scenarios/index.ts.html +109 -0
- package/reports/scenarios/one-admin/export-navigation.scenario.ts.html +199 -0
- package/reports/scenarios/one-admin/index.html +131 -0
- package/reports/scenarios/one-admin/index.ts.html +103 -0
- package/reports/scenarios/one-admin/page-models/ExportPageModel.ts.html +163 -0
- package/reports/scenarios/one-admin/page-models/SelectSettingsPageModel.ts.html +175 -0
- package/reports/scenarios/one-admin/page-models/index.html +131 -0
- package/reports/sort-arrow-sprite.png +0 -0
- package/reports/sorter.js +210 -0
- package/reports/types/config.ts.html +187 -0
- package/reports/types/index.html +116 -0
- package/stylelint.config.cjs +2 -0
- package/test-report.xml +100 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { BatchRunner } from '../batchRunner.js';
|
|
2
|
+
import { ScenarioRunner } from '../scenarioRunner.js';
|
|
3
|
+
import { ScenarioRegistry } from '../../registry/scenarioRegistry.js';
|
|
4
|
+
import type { MicroappLeakScenario } from '../../types/scenario.js';
|
|
5
|
+
import type { ScenarioResult } from '../../types/results.js';
|
|
6
|
+
import type { RunnerConfig } from '../../types/config.js';
|
|
7
|
+
|
|
8
|
+
jest.mock('../scenarioRunner.js');
|
|
9
|
+
|
|
10
|
+
function makeConfig(): RunnerConfig {
|
|
11
|
+
return {
|
|
12
|
+
env: {
|
|
13
|
+
baseUrl: 'https://q3.elliemae.io',
|
|
14
|
+
instanceId: 'BE11226875',
|
|
15
|
+
userId: 'admin',
|
|
16
|
+
password: 'Password#!23',
|
|
17
|
+
},
|
|
18
|
+
runner: {
|
|
19
|
+
headless: true,
|
|
20
|
+
outputDir: '/tmp/leak-reports',
|
|
21
|
+
topN: 5,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function makeScenario(id: string, tags: string[] = []): MicroappLeakScenario {
|
|
27
|
+
return {
|
|
28
|
+
id,
|
|
29
|
+
name: id.replace(/-/g, ' '),
|
|
30
|
+
description: `${id} description`,
|
|
31
|
+
tags,
|
|
32
|
+
microappSelector: `iframe#${id}`,
|
|
33
|
+
url: () => `/${id}`,
|
|
34
|
+
action: jest.fn().mockResolvedValue(undefined),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function makeResult(name: string, passed: boolean): ScenarioResult {
|
|
39
|
+
return {
|
|
40
|
+
name,
|
|
41
|
+
passed,
|
|
42
|
+
thresholdResult: { passed, reason: passed ? null : 'threshold exceeded' },
|
|
43
|
+
report: null,
|
|
44
|
+
durationMs: 1000,
|
|
45
|
+
error: null,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildRegistry(scenarios: MicroappLeakScenario[]): ScenarioRegistry {
|
|
50
|
+
return new ScenarioRegistry().register({
|
|
51
|
+
microapp: 'one-admin',
|
|
52
|
+
scenarios,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('BatchRunner', () => {
|
|
57
|
+
let runMock: jest.Mock;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
runMock = jest.fn();
|
|
61
|
+
(ScenarioRunner as jest.Mock).mockImplementation(() => ({
|
|
62
|
+
launchBrowser: jest.fn().mockResolvedValue({
|
|
63
|
+
close: jest.fn().mockResolvedValue(undefined),
|
|
64
|
+
}),
|
|
65
|
+
run: runMock,
|
|
66
|
+
}));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('runs all scenarios when no filter applied', async () => {
|
|
70
|
+
const registry = buildRegistry([
|
|
71
|
+
makeScenario('alpha'),
|
|
72
|
+
makeScenario('beta'),
|
|
73
|
+
]);
|
|
74
|
+
runMock
|
|
75
|
+
.mockResolvedValueOnce(makeResult('alpha', true))
|
|
76
|
+
.mockResolvedValueOnce(makeResult('beta', true));
|
|
77
|
+
|
|
78
|
+
const runner = new BatchRunner(makeConfig(), registry);
|
|
79
|
+
const summary = await runner.runAll();
|
|
80
|
+
|
|
81
|
+
expect(runMock).toHaveBeenCalledTimes(2);
|
|
82
|
+
expect(summary.results).toHaveLength(2);
|
|
83
|
+
expect(summary.passCount).toBe(2);
|
|
84
|
+
expect(summary.failCount).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('filters scenarios by tag', async () => {
|
|
88
|
+
const registry = buildRegistry([
|
|
89
|
+
makeScenario('alpha', ['critical']),
|
|
90
|
+
makeScenario('beta', ['optional']),
|
|
91
|
+
]);
|
|
92
|
+
runMock.mockResolvedValueOnce(makeResult('alpha', true));
|
|
93
|
+
|
|
94
|
+
const runner = new BatchRunner(makeConfig(), registry);
|
|
95
|
+
const summary = await runner.runByTag('critical');
|
|
96
|
+
|
|
97
|
+
expect(runMock).toHaveBeenCalledTimes(1);
|
|
98
|
+
expect(summary.results[0].name).toBe('alpha');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('runs a single scenario by registry key', async () => {
|
|
102
|
+
const registry = buildRegistry([
|
|
103
|
+
makeScenario('alpha'),
|
|
104
|
+
makeScenario('beta'),
|
|
105
|
+
]);
|
|
106
|
+
runMock.mockResolvedValueOnce(makeResult('alpha', false));
|
|
107
|
+
|
|
108
|
+
const runner = new BatchRunner(makeConfig(), registry);
|
|
109
|
+
const summary = await runner.runByName('one-admin/alpha');
|
|
110
|
+
|
|
111
|
+
expect(runMock).toHaveBeenCalledTimes(1);
|
|
112
|
+
expect(summary.failCount).toBe(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('throws when scenario key is not in the registry', async () => {
|
|
116
|
+
const runner = new BatchRunner(makeConfig(), new ScenarioRegistry());
|
|
117
|
+
await expect(runner.runByName('one-admin/unknown')).rejects.toThrow(
|
|
118
|
+
'No scenario registered with key "one-admin/unknown"',
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('totalDurationMs is sum of all scenario durations', async () => {
|
|
123
|
+
const registry = buildRegistry([makeScenario('a'), makeScenario('b')]);
|
|
124
|
+
runMock
|
|
125
|
+
.mockResolvedValueOnce({ ...makeResult('a', true), durationMs: 3000 })
|
|
126
|
+
.mockResolvedValueOnce({ ...makeResult('b', true), durationMs: 4000 });
|
|
127
|
+
|
|
128
|
+
const runner = new BatchRunner(makeConfig(), registry);
|
|
129
|
+
const summary = await runner.runAll();
|
|
130
|
+
|
|
131
|
+
expect(summary.totalDurationMs).toBe(7000);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { Browser } from '@playwright/test';
|
|
2
|
+
import { ScenarioRunner } from '../scenarioRunner.js';
|
|
3
|
+
import type { RunnerConfig } from '../../types/config.js';
|
|
4
|
+
import type { MicroappLeakScenario } from '../../types/scenario.js';
|
|
5
|
+
|
|
6
|
+
function makeConfig(overrides: Partial<RunnerConfig> = {}): RunnerConfig {
|
|
7
|
+
return {
|
|
8
|
+
env: {
|
|
9
|
+
baseUrl: 'https://q3.elliemae.io',
|
|
10
|
+
instanceId: 'BE11226875',
|
|
11
|
+
userId: 'admin',
|
|
12
|
+
password: 'Password#!23',
|
|
13
|
+
},
|
|
14
|
+
runner: {
|
|
15
|
+
headless: true,
|
|
16
|
+
outputDir: '/tmp/leak-reports-test',
|
|
17
|
+
topN: 5,
|
|
18
|
+
},
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeScenario(
|
|
24
|
+
overrides: Partial<MicroappLeakScenario> = {},
|
|
25
|
+
): MicroappLeakScenario {
|
|
26
|
+
return {
|
|
27
|
+
id: 'test-scenario',
|
|
28
|
+
name: 'test-scenario',
|
|
29
|
+
description: 'Test scenario',
|
|
30
|
+
microappSelector: 'iframe#test-app',
|
|
31
|
+
url: () => 'https://q3.elliemae.io/test',
|
|
32
|
+
action: jest.fn().mockResolvedValue(undefined),
|
|
33
|
+
back: jest.fn().mockResolvedValue(undefined),
|
|
34
|
+
repeat: () => 1,
|
|
35
|
+
thresholds: {
|
|
36
|
+
maxRetainedSizeDeltaBytes: 10 * 1024 * 1024,
|
|
37
|
+
maxNewLeakGroups: 0,
|
|
38
|
+
},
|
|
39
|
+
...overrides,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
jest.mock('@elliemae/smoked-suite', () => {
|
|
44
|
+
// HeapMemoryProfiler must be a real class because IframeHeapProfiler extends it
|
|
45
|
+
class HeapMemoryProfiler {
|
|
46
|
+
constructor(
|
|
47
|
+
private readonly page: unknown,
|
|
48
|
+
private readonly outputDir: string,
|
|
49
|
+
) {}
|
|
50
|
+
|
|
51
|
+
protected getCDPTarget(): unknown {
|
|
52
|
+
return this.page;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
captureSnapshot(label: string): Promise<string> {
|
|
56
|
+
return Promise.resolve(`${this.outputDir}/${label}.heapsnapshot`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compare(): Promise<null> {
|
|
60
|
+
return Promise.resolve(null);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
HeapMemoryProfiler,
|
|
66
|
+
AuthManager: jest.fn().mockImplementation(() => ({
|
|
67
|
+
login: jest.fn().mockResolvedValue(undefined),
|
|
68
|
+
})),
|
|
69
|
+
PageSetup: jest.fn().mockImplementation(() => ({
|
|
70
|
+
apply: jest.fn().mockResolvedValue(undefined),
|
|
71
|
+
})),
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('ScenarioRunner', () => {
|
|
76
|
+
it('constructs without throwing', () => {
|
|
77
|
+
expect(() => new ScenarioRunner(makeConfig())).not.toThrow();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('run() returns a ScenarioResult with error when browser interactions fail', async () => {
|
|
81
|
+
const runner = new ScenarioRunner(makeConfig());
|
|
82
|
+
const scenario = makeScenario();
|
|
83
|
+
|
|
84
|
+
const mockPage = {
|
|
85
|
+
context: jest.fn().mockReturnValue({
|
|
86
|
+
browser: () => ({ browserType: () => ({ name: () => 'chromium' }) }),
|
|
87
|
+
newCDPSession: jest.fn().mockResolvedValue({
|
|
88
|
+
send: jest.fn().mockResolvedValue(undefined),
|
|
89
|
+
on: jest.fn(),
|
|
90
|
+
detach: jest.fn().mockResolvedValue(undefined),
|
|
91
|
+
}),
|
|
92
|
+
}),
|
|
93
|
+
goto: jest.fn().mockRejectedValue(new Error('Navigation failed')),
|
|
94
|
+
waitForTimeout: jest.fn().mockResolvedValue(undefined),
|
|
95
|
+
locator: jest.fn().mockReturnValue({
|
|
96
|
+
elementHandle: jest.fn().mockResolvedValue(null),
|
|
97
|
+
}),
|
|
98
|
+
addInitScript: jest.fn().mockResolvedValue(undefined),
|
|
99
|
+
route: jest.fn().mockResolvedValue(undefined),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const mockBrowser = {
|
|
103
|
+
newContext: jest.fn().mockResolvedValue({
|
|
104
|
+
newPage: jest.fn().mockResolvedValue(mockPage),
|
|
105
|
+
close: jest.fn().mockResolvedValue(undefined),
|
|
106
|
+
}),
|
|
107
|
+
} as unknown as Browser;
|
|
108
|
+
|
|
109
|
+
const result = await runner.run(scenario, mockBrowser);
|
|
110
|
+
|
|
111
|
+
expect(result.name).toBe('test-scenario');
|
|
112
|
+
expect(typeof result.durationMs).toBe('number');
|
|
113
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
114
|
+
expect(typeof result.passed).toBe('boolean');
|
|
115
|
+
// With navigation failure, should have error set
|
|
116
|
+
expect(result.error).not.toBeNull();
|
|
117
|
+
expect(result.passed).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('run() returns a ScenarioResult with the correct shape on any outcome', async () => {
|
|
121
|
+
const runner = new ScenarioRunner(makeConfig());
|
|
122
|
+
const scenario = makeScenario();
|
|
123
|
+
|
|
124
|
+
const mockBrowser = {
|
|
125
|
+
newContext: jest.fn().mockResolvedValue({
|
|
126
|
+
newPage: jest.fn().mockResolvedValue({
|
|
127
|
+
context: jest.fn().mockReturnValue({
|
|
128
|
+
browser: () => ({
|
|
129
|
+
browserType: () => ({ name: () => 'chromium' }),
|
|
130
|
+
}),
|
|
131
|
+
newCDPSession: jest.fn().mockResolvedValue({
|
|
132
|
+
send: jest.fn().mockResolvedValue(undefined),
|
|
133
|
+
on: jest.fn(),
|
|
134
|
+
detach: jest.fn().mockResolvedValue(undefined),
|
|
135
|
+
}),
|
|
136
|
+
}),
|
|
137
|
+
goto: jest.fn().mockRejectedValue(new Error('Page load failed')),
|
|
138
|
+
waitForTimeout: jest.fn().mockResolvedValue(undefined),
|
|
139
|
+
locator: jest.fn().mockReturnValue({
|
|
140
|
+
elementHandle: jest.fn().mockResolvedValue(null),
|
|
141
|
+
}),
|
|
142
|
+
addInitScript: jest.fn().mockResolvedValue(undefined),
|
|
143
|
+
route: jest.fn().mockResolvedValue(undefined),
|
|
144
|
+
}),
|
|
145
|
+
close: jest.fn().mockResolvedValue(undefined),
|
|
146
|
+
}),
|
|
147
|
+
} as unknown as Browser;
|
|
148
|
+
|
|
149
|
+
const result = await runner.run(scenario, mockBrowser);
|
|
150
|
+
|
|
151
|
+
// ScenarioResult shape
|
|
152
|
+
expect(result).toHaveProperty('name');
|
|
153
|
+
expect(result).toHaveProperty('passed');
|
|
154
|
+
expect(result).toHaveProperty('thresholdResult');
|
|
155
|
+
expect(result).toHaveProperty('report');
|
|
156
|
+
expect(result).toHaveProperty('durationMs');
|
|
157
|
+
expect(result).toHaveProperty('error');
|
|
158
|
+
|
|
159
|
+
expect(result.thresholdResult).toHaveProperty('passed');
|
|
160
|
+
expect(result.thresholdResult).toHaveProperty('reason');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ScenarioRegistry } from '../registry/scenarioRegistry.js';
|
|
2
|
+
import { oneAdminScenarios } from './one-admin/index.js';
|
|
3
|
+
|
|
4
|
+
export const scenarioRegistry: ScenarioRegistry =
|
|
5
|
+
new ScenarioRegistry().register({
|
|
6
|
+
microapp: 'one-admin',
|
|
7
|
+
scenarios: oneAdminScenarios,
|
|
8
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { MicroappLeakScenario } from '../../types/scenario.js';
|
|
2
|
+
import {
|
|
3
|
+
SelectSettingsPageModel,
|
|
4
|
+
ExportPageModel,
|
|
5
|
+
} from './page-models/index.js';
|
|
6
|
+
|
|
7
|
+
export const exportNavigationScenario: MicroappLeakScenario = {
|
|
8
|
+
id: 'export-navigation',
|
|
9
|
+
name: 'Export Navigation',
|
|
10
|
+
description: 'Navigate to export page and come back - verify iframe GC',
|
|
11
|
+
tags: ['critical'],
|
|
12
|
+
microappSelector: 'iframe#pui-iframe-container-emAdminUI',
|
|
13
|
+
|
|
14
|
+
url: () => '/admin/oneadmin/migrate',
|
|
15
|
+
|
|
16
|
+
async action(page, frame) {
|
|
17
|
+
const settings = new SelectSettingsPageModel(frame);
|
|
18
|
+
await settings.container.waitFor({ state: 'visible' });
|
|
19
|
+
await settings.expandTreeItem('eFolder');
|
|
20
|
+
await settings.selectTreeItem('Enhanced Conditions');
|
|
21
|
+
await settings.clickExport();
|
|
22
|
+
|
|
23
|
+
const exportPage = new ExportPageModel(frame);
|
|
24
|
+
await exportPage.sourceDataTab.waitFor({ state: 'visible' });
|
|
25
|
+
await exportPage.dataTable.waitFor({ state: 'visible' });
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async back(page) {
|
|
29
|
+
await ExportPageModel.clickBackAndWaitForSettings(page);
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
repeat: () => 3,
|
|
33
|
+
|
|
34
|
+
thresholds: {
|
|
35
|
+
maxRetainedSizeDeltaBytes: 10 * 1024 * 1024,
|
|
36
|
+
maxNewLeakGroups: 0,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Frame, Locator, Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
const IFRAME_SELECTOR = 'iframe#pui-iframe-container-emAdminUI';
|
|
4
|
+
const SETTINGS_URL_PATTERN = /\/admin\/oneadmin\/migrate(?:\/)?$/;
|
|
5
|
+
|
|
6
|
+
export class ExportPageModel {
|
|
7
|
+
constructor(private readonly frame: Frame) {}
|
|
8
|
+
|
|
9
|
+
get sourceDataTab(): Locator {
|
|
10
|
+
return this.frame.getByRole('tab', { name: /Source Data/i });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get dataTable(): Locator {
|
|
14
|
+
return this.frame.locator('[role="table"]').first();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static async clickBackAndWaitForSettings(page: Page): Promise<void> {
|
|
18
|
+
await Promise.all([
|
|
19
|
+
page.waitForURL(SETTINGS_URL_PATTERN),
|
|
20
|
+
page
|
|
21
|
+
.frameLocator(IFRAME_SELECTOR)
|
|
22
|
+
.getByRole('button', { name: /back/i })
|
|
23
|
+
.click(),
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Frame, Locator } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export class SelectSettingsPageModel {
|
|
4
|
+
constructor(private readonly frame: Frame) {}
|
|
5
|
+
|
|
6
|
+
get container(): Locator {
|
|
7
|
+
return this.frame.locator('#one-admin-console-container');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get exportButton(): Locator {
|
|
11
|
+
return this.frame.getByRole('button', { name: 'Export' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async expandTreeItem(name: string): Promise<void> {
|
|
15
|
+
const treeItem = this.frame.locator(
|
|
16
|
+
`[role="treeitem"]:has-text("${name}")`,
|
|
17
|
+
);
|
|
18
|
+
await treeItem
|
|
19
|
+
.locator('button[aria-label*="expand"], button[aria-label*="toggle"]')
|
|
20
|
+
.click();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async selectTreeItem(name: string): Promise<void> {
|
|
24
|
+
await this.frame.locator(`[role="treeitem"]:has-text("${name}")`).click();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async clickExport(): Promise<void> {
|
|
28
|
+
await this.exportButton.click();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface EnvironmentParams {
|
|
2
|
+
readonly baseUrl: string;
|
|
3
|
+
readonly instanceId: string;
|
|
4
|
+
readonly userId: string;
|
|
5
|
+
readonly password: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RunnerOptions {
|
|
9
|
+
readonly headless: boolean;
|
|
10
|
+
readonly outputDir: string;
|
|
11
|
+
readonly topN: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AiConfig {
|
|
15
|
+
readonly model: string;
|
|
16
|
+
readonly temperature: number;
|
|
17
|
+
readonly apiKey: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RunnerConfig {
|
|
21
|
+
readonly env: EnvironmentParams;
|
|
22
|
+
readonly runner: RunnerOptions;
|
|
23
|
+
readonly ai?: AiConfig;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ResolvedThresholds {
|
|
27
|
+
maxRetainedSizeDeltaBytes: number;
|
|
28
|
+
maxNewLeakGroups: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_THRESHOLDS: ResolvedThresholds = {
|
|
32
|
+
maxRetainedSizeDeltaBytes: 10 * 1024 * 1024,
|
|
33
|
+
maxNewLeakGroups: 0,
|
|
34
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ComparisonReport } from '@elliemae/encw-heap-doctor';
|
|
2
|
+
|
|
3
|
+
export interface ThresholdResult {
|
|
4
|
+
readonly passed: boolean;
|
|
5
|
+
readonly reason: string | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ScenarioResult {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly passed: boolean;
|
|
11
|
+
readonly thresholdResult: ThresholdResult;
|
|
12
|
+
readonly report: ComparisonReport | null;
|
|
13
|
+
readonly durationMs: number;
|
|
14
|
+
readonly error: Error | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RunSummary {
|
|
18
|
+
readonly results: readonly ScenarioResult[];
|
|
19
|
+
readonly totalDurationMs: number;
|
|
20
|
+
readonly passCount: number;
|
|
21
|
+
readonly failCount: number;
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Page, Frame } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export interface MicroappLeakScenario {
|
|
4
|
+
/** Kebab-case identifier used as the scenario portion of the registry key. */
|
|
5
|
+
readonly id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
tags?: string[];
|
|
9
|
+
microappSelector: string;
|
|
10
|
+
url(): string;
|
|
11
|
+
action(page: Page, frame: Frame): Promise<void>;
|
|
12
|
+
back?(page: Page): Promise<void>;
|
|
13
|
+
repeat?(): number;
|
|
14
|
+
thresholds?: {
|
|
15
|
+
maxRetainedSizeDeltaBytes?: number;
|
|
16
|
+
maxNewLeakGroups?: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elliemae/encw-leak-runner",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Playwright orchestration framework for microapp memory leak detection",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"types": "./dist/types/lib/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"leak-runner": "./dist/bin/leak-runner.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/types/lib/index.d.ts",
|
|
15
|
+
"import": "./dist/esm/index.js",
|
|
16
|
+
"require": "./dist/cjs/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"commander": "^12.1.0",
|
|
21
|
+
"zod": "^3.25.76",
|
|
22
|
+
"@elliemae/encw-heap-doctor": "26.2.15",
|
|
23
|
+
"@elliemae/smoked-suite": "26.2.26"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@elliemae/pui-cli": "~8.59.3",
|
|
27
|
+
"@playwright/test": "~1.58.2",
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"typescript": "^5.6.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@playwright/test": ">=1.40.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "pui-cli pack -p -t node && esbuild bin/leak-runner.ts --bundle --packages=external --platform=node --target=node18 --format=esm --outdir=dist/bin",
|
|
39
|
+
"dev": "pui-cli pack -t node && esbuild bin/leak-runner.ts --bundle --packages=external --platform=node --target=node18 --format=esm --outdir=dist/bin",
|
|
40
|
+
"test": "pui-cli test -p",
|
|
41
|
+
"test-watch": "jest --watch",
|
|
42
|
+
"lint": "pui-cli lint",
|
|
43
|
+
"lint:fix": "pui-cli lint --fix",
|
|
44
|
+
"tscheck": "pui-cli tscheck --files"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
|
|
5
|
+
<head>
|
|
6
|
+
<title>Code coverage report for analysis</title>
|
|
7
|
+
<meta charset="utf-8" />
|
|
8
|
+
<link rel="stylesheet" href="../prettify.css" />
|
|
9
|
+
<link rel="stylesheet" href="../base.css" />
|
|
10
|
+
<link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
12
|
+
<style type='text/css'>
|
|
13
|
+
.coverage-summary .sorter {
|
|
14
|
+
background-image: url(../sort-arrow-sprite.png);
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
|
|
19
|
+
<body>
|
|
20
|
+
<div class='wrapper'>
|
|
21
|
+
<div class='pad1'>
|
|
22
|
+
<h1><a href="../index.html">All files</a> analysis</h1>
|
|
23
|
+
<div class='clearfix'>
|
|
24
|
+
|
|
25
|
+
<div class='fl pad1y space-right2'>
|
|
26
|
+
<span class="strong">100% </span>
|
|
27
|
+
<span class="quiet">Statements</span>
|
|
28
|
+
<span class='fraction'>11/11</span>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<div class='fl pad1y space-right2'>
|
|
33
|
+
<span class="strong">100% </span>
|
|
34
|
+
<span class="quiet">Branches</span>
|
|
35
|
+
<span class='fraction'>3/3</span>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
<div class='fl pad1y space-right2'>
|
|
40
|
+
<span class="strong">100% </span>
|
|
41
|
+
<span class="quiet">Functions</span>
|
|
42
|
+
<span class='fraction'>2/2</span>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<div class='fl pad1y space-right2'>
|
|
47
|
+
<span class="strong">100% </span>
|
|
48
|
+
<span class="quiet">Lines</span>
|
|
49
|
+
<span class='fraction'>11/11</span>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
<p class="quiet">
|
|
55
|
+
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
|
56
|
+
</p>
|
|
57
|
+
<template id="filterTemplate">
|
|
58
|
+
<div class="quiet">
|
|
59
|
+
Filter:
|
|
60
|
+
<input type="search" id="fileSearch">
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
63
|
+
</div>
|
|
64
|
+
<div class='status-line high'></div>
|
|
65
|
+
<div class="pad1">
|
|
66
|
+
<table class="coverage-summary">
|
|
67
|
+
<thead>
|
|
68
|
+
<tr>
|
|
69
|
+
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
|
70
|
+
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
|
71
|
+
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
|
72
|
+
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
73
|
+
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
|
74
|
+
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
75
|
+
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
|
76
|
+
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
77
|
+
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
|
78
|
+
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
79
|
+
</tr>
|
|
80
|
+
</thead>
|
|
81
|
+
<tbody><tr>
|
|
82
|
+
<td class="file high" data-value="thresholdEvaluator.ts"><a href="thresholdEvaluator.ts.html">thresholdEvaluator.ts</a></td>
|
|
83
|
+
<td data-value="100" class="pic high">
|
|
84
|
+
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
|
|
85
|
+
</td>
|
|
86
|
+
<td data-value="100" class="pct high">100%</td>
|
|
87
|
+
<td data-value="11" class="abs high">11/11</td>
|
|
88
|
+
<td data-value="100" class="pct high">100%</td>
|
|
89
|
+
<td data-value="3" class="abs high">3/3</td>
|
|
90
|
+
<td data-value="100" class="pct high">100%</td>
|
|
91
|
+
<td data-value="2" class="abs high">2/2</td>
|
|
92
|
+
<td data-value="100" class="pct high">100%</td>
|
|
93
|
+
<td data-value="11" class="abs high">11/11</td>
|
|
94
|
+
</tr>
|
|
95
|
+
|
|
96
|
+
</tbody>
|
|
97
|
+
</table>
|
|
98
|
+
</div>
|
|
99
|
+
<div class='push'></div><!-- for sticky footer -->
|
|
100
|
+
</div><!-- /wrapper -->
|
|
101
|
+
<div class='footer quiet pad2 space-top1 center small'>
|
|
102
|
+
Code coverage generated by
|
|
103
|
+
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
104
|
+
at 2026-05-08T18:03:27.910Z
|
|
105
|
+
</div>
|
|
106
|
+
<script src="../prettify.js"></script>
|
|
107
|
+
<script>
|
|
108
|
+
window.onload = function () {
|
|
109
|
+
prettyPrint();
|
|
110
|
+
};
|
|
111
|
+
</script>
|
|
112
|
+
<script src="../sorter.js"></script>
|
|
113
|
+
<script src="../block-navigation.js"></script>
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
116
|
+
|