ui-coverage-scenario-tool-js
Version:
**UI Coverage Scenario Tool** is an innovative, no-overhead solution for tracking and visualizing UI test coverage — directly on your actual application, not static snapshots. The tool collects coverage during UI test execution and generates an interactiv
327 lines (314 loc) • 10.6 kB
JavaScript
// src/tools/actions.ts
var ActionType = /* @__PURE__ */ ((ActionType2) => {
ActionType2["Fill"] = "FILL";
ActionType2["Type"] = "TYPE";
ActionType2["Select"] = "SELECT";
ActionType2["Click"] = "CLICK";
ActionType2["Hover"] = "HOVER";
ActionType2["Text"] = "TEXT";
ActionType2["Value"] = "VALUE";
ActionType2["Hidden"] = "HIDDEN";
ActionType2["Visible"] = "VISIBLE";
ActionType2["Checked"] = "CHECKED";
ActionType2["Enabled"] = "ENABLED";
ActionType2["Disabled"] = "DISABLED";
ActionType2["Unchecked"] = "UNCHECKED";
return ActionType2;
})(ActionType || {});
// src/tools/logger.ts
var getLogger = (name) => ({
info: (msg) => console.info(`[${name}] ${msg}`),
debug: (msg) => console.debug(`[${name}] ${msg}`),
error: (msg) => console.error(`[${name}] ${msg}`),
warning: (msg) => console.warn(`[${name}] ${msg}`)
});
// src/tracker/storage.ts
import fs2 from "fs/promises";
import path from "path";
import { v4 as uuidv4 } from "uuid";
// src/tools/files.ts
import fs from "fs";
import yaml from "js-yaml";
import fsAsync from "fs/promises";
var logger = getLogger("FILES");
var isPathExists = async (path3) => {
try {
await fsAsync.access(path3);
return true;
} catch (error) {
return false;
}
};
var loadFromJson = (file) => {
try {
if (!fs.existsSync(file)) return {};
const raw = fs.readFileSync(file, "utf-8");
return JSON.parse(raw);
} catch (error) {
logger.warning(`Failed to load JSON config ${file}: ${error}`);
return {};
}
};
var loadFromYaml = (file) => {
try {
if (!fs.existsSync(file)) return {};
const raw = fs.readFileSync(file, "utf-8");
return yaml.load(raw);
} catch (error) {
logger.warning(`Failed to load YAML config ${file}: ${error}`);
return {};
}
};
// src/tracker/models/pages.ts
var CoveragePageResultList = class _CoveragePageResultList {
constructor({ results }) {
this.results = results;
}
filter({ app }) {
const filtered = this.results.filter((result) => !app || result.app.toLowerCase() === app.toLowerCase());
return new _CoveragePageResultList({ results: filtered });
}
unique() {
const map = /* @__PURE__ */ new Map();
for (const result of this.results) {
const key = result.page;
if (!map.has(key)) {
map.set(key, result);
}
}
return new _CoveragePageResultList({ results: Array.from(map.values()) });
}
findScenarios({ page }) {
const scenarios = this.results.filter((result) => result.page === page).map((result) => result.scenario);
return Array.from(new Set(scenarios));
}
};
// src/tracker/models/elements.ts
var CoverageElementResultList = class _CoverageElementResultList {
constructor({ results }) {
this.results = results;
}
filter({ app, scenario }) {
const filtered = this.results.filter(
(result) => (!app || result.app.toLowerCase() === app.toLowerCase()) && (!scenario || result.scenario.toLowerCase() === scenario.toLowerCase())
);
return new _CoverageElementResultList({ results: filtered });
}
get groupedByAction() {
return this.groupBy((r) => r.actionType);
}
get groupedBySelector() {
return this.groupBy((r) => `${encodeURIComponent(r.selector)}|${r.selectorType}`);
}
get totalActions() {
return this.results.length;
}
get totalSelectors() {
return this.groupedBySelector.size;
}
countAction(actionType) {
return this.results.filter((r) => r.actionType === actionType).length;
}
groupBy(keyGetter) {
const map = /* @__PURE__ */ new Map();
for (const result of this.results) {
const key = keyGetter(result);
const results = map.get(key) || [];
results.push(result);
map.set(key, results);
}
const resultMap = /* @__PURE__ */ new Map();
for (const [key, results] of map.entries()) {
resultMap.set(key, new _CoverageElementResultList({ results }));
}
return resultMap;
}
};
// src/tracker/models/scenarios.ts
var CoverageScenarioResultList = class _CoverageScenarioResultList {
constructor({ results }) {
this.results = results;
}
filter({ app }) {
const filtered = this.results.filter((result) => !app || result.app.toLowerCase() === app.toLowerCase());
return new _CoverageScenarioResultList({ results: filtered });
}
};
// src/tracker/models/transitions.ts
var CoverageTransitionResultList = class _CoverageTransitionResultList {
constructor({ results }) {
this.results = results;
}
filter({ app }) {
const filtered = this.results.filter((result) => !app || result.app.toLowerCase() === app.toLowerCase());
return new _CoverageTransitionResultList({ results: filtered });
}
unique() {
const map = /* @__PURE__ */ new Map();
for (const result of this.results) {
const key = `${result.fromPage}\u2192${result.toPage}`;
if (!map.has(key)) {
map.set(key, result);
}
}
return new _CoverageTransitionResultList({ results: Array.from(map.values()) });
}
findScenarios({ toPage, fromPage }) {
const scenarios = this.results.filter((result) => result.toPage === toPage && result.fromPage === fromPage).map((result) => result.scenario);
return Array.from(new Set(scenarios));
}
countTransitions({ toPage, fromPage }) {
return this.results.filter((result) => result.toPage === toPage && result.fromPage === fromPage).length;
}
};
// src/tracker/storage.ts
var logger2 = getLogger("UI_COVERAGE_TRACKER_STORAGE");
var UICoverageTrackerStorage = class {
constructor({ settings }) {
this.settings = settings;
}
async load(props) {
const { context, resultList } = props;
const resultsDir = this.settings.resultsDir;
logger2.info(`Loading coverage results from directory: ${resultsDir}`);
if (!await isPathExists(resultsDir)) {
logger2.warning(`Results directory does not exist: ${resultsDir}`);
return new resultList({ results: [] });
}
const results = [];
for (const fileName of await fs2.readdir(resultsDir)) {
const file = path.join(resultsDir, fileName);
const fileStats = await fs2.stat(file);
if (fileStats.isFile() && fileName.endsWith(`-${context}.json`)) {
try {
const json = await fs2.readFile(file, "utf-8");
results.push(JSON.parse(json));
} catch (error) {
logger2.warning(`Failed to parse file ${fileName}: ${error}`);
}
}
}
logger2.info(`Loaded ${results.length} coverage files from directory: ${resultsDir}`);
return new resultList({ results });
}
async save({ result, context }) {
const resultsDir = this.settings.resultsDir;
if (!await isPathExists(resultsDir)) {
logger2.info(`Results directory does not exist, creating: ${resultsDir}`);
await fs2.mkdir(resultsDir, { recursive: true });
}
const file = path.join(resultsDir, `${uuidv4()}-${context}.json`);
try {
await fs2.writeFile(file, JSON.stringify(result), "utf-8");
} catch (error) {
logger2.error(`Error saving coverage data to file ${file}: ${error}`);
}
}
async savePageResult(result) {
await this.save({ context: "page", result });
}
async saveElementResult(result) {
await this.save({ context: "element", result });
}
async saveScenarioResult(result) {
await this.save({ context: "scenario", result });
}
async saveTransitionResult(result) {
await this.save({ context: "transition", result });
}
async loadPageResults() {
return await this.load({ context: "page", resultList: CoveragePageResultList });
}
async loadElementResults() {
return await this.load({ context: "element", resultList: CoverageElementResultList });
}
async loadScenarioResults() {
return await this.load({ context: "scenario", resultList: CoverageScenarioResultList });
}
async loadTransitionResults() {
return await this.load({ context: "transition", resultList: CoverageTransitionResultList });
}
};
// src/tools/json.ts
var logger3 = getLogger("JSON");
var loadJson = ({ content, fallback }) => {
try {
return JSON.parse(content, (key, value) => {
switch (key) {
case "createdAt":
return new Date(value);
default:
return value;
}
});
} catch (error) {
logger3.warning(`Failed to parse JSON: ${error}`);
return fallback;
}
};
// src/config/builders.ts
import path2 from "path";
import url from "url";
import dotenv from "dotenv";
dotenv.config();
var cwd = process.cwd();
var cleanUndefined = (input) => {
return Object.fromEntries(Object.entries(input).filter(([_, v]) => v !== void 0));
};
var buildEnvSettings = () => {
return cleanUndefined({
apps: loadJson({ content: process.env.UI_COVERAGE_SCENARIO_APPS || "", fallback: [] }),
resultsDir: process.env.UI_COVERAGE_SCENARIO_RESULTS_DIR || void 0,
historyFile: process.env.UI_COVERAGE_SCENARIO_HISTORY_FILE || void 0,
historyRetentionLimit: parseInt(process.env.UI_COVERAGE_SCENARIO_HISTORY_RETENTION_LIMIT || "", 10) || void 0,
htmlReportFile: process.env.UI_COVERAGE_SCENARIO_HTML_REPORT_FILE || void 0,
jsonReportFile: process.env.UI_COVERAGE_SCENARIO_JSON_REPORT_FILE || void 0
});
};
var buildJsonSettings = () => {
return cleanUndefined(loadFromJson(path2.join(cwd, "ui-coverage-scenario.config.json")));
};
var buildYamlSettings = () => {
return cleanUndefined(loadFromYaml(path2.join(cwd, "ui-coverage-scenario.config.yaml")));
};
var buildDefaultSettings = () => {
let htmlReportTemplateFile;
try {
htmlReportTemplateFile = path2.join(
path2.dirname(url.fileURLToPath(import.meta.url)),
"reports/templates/index.html"
);
} catch {
htmlReportTemplateFile = path2.join(cwd, "src/reports/templates/index.html");
}
return {
apps: [],
resultsDir: path2.join(cwd, "coverage-results"),
historyFile: path2.join(cwd, "coverage-history.json"),
historyRetentionLimit: 30,
htmlReportFile: path2.join(cwd, "index.html"),
jsonReportFile: path2.join(cwd, "coverage-report.json"),
htmlReportTemplateFile
};
};
// src/config/core.ts
var getSettings = () => {
const defaultSettings = buildDefaultSettings();
return {
...defaultSettings,
...buildYamlSettings(),
...buildJsonSettings(),
...buildEnvSettings(),
htmlReportTemplateFile: defaultSettings.htmlReportTemplateFile
};
};
export {
ActionType,
getLogger,
isPathExists,
UICoverageTrackerStorage,
loadJson,
getSettings
};
//# sourceMappingURL=chunk-KWTIKUCY.js.map