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
434 lines (419 loc) • 14.9 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
ActionType: () => ActionType,
SelectorType: () => SelectorType,
UICoverageTracker: () => UICoverageTracker
});
module.exports = __toCommonJS(src_exports);
// 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/selector.ts
var SelectorType = /* @__PURE__ */ ((SelectorType2) => {
SelectorType2["CSS"] = "CSS";
SelectorType2["XPath"] = "XPATH";
return SelectorType2;
})(SelectorType || {});
// src/tracker/storage.ts
var import_promises2 = __toESM(require("fs/promises"), 1);
var import_path = __toESM(require("path"), 1);
var import_uuid = require("uuid");
// 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/tools/files.ts
var import_fs = __toESM(require("fs"), 1);
var import_js_yaml = __toESM(require("js-yaml"), 1);
var import_promises = __toESM(require("fs/promises"), 1);
var logger = getLogger("FILES");
var isPathExists = async (path3) => {
try {
await import_promises.default.access(path3);
return true;
} catch (error) {
return false;
}
};
var loadFromJson = (file) => {
try {
if (!import_fs.default.existsSync(file)) return {};
const raw = import_fs.default.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 (!import_fs.default.existsSync(file)) return {};
const raw = import_fs.default.readFileSync(file, "utf-8");
return import_js_yaml.default.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 import_promises2.default.readdir(resultsDir)) {
const file = import_path.default.join(resultsDir, fileName);
const fileStats = await import_promises2.default.stat(file);
if (fileStats.isFile() && fileName.endsWith(`-${context}.json`)) {
try {
const json = await import_promises2.default.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 import_promises2.default.mkdir(resultsDir, { recursive: true });
}
const file = import_path.default.join(resultsDir, `${(0, import_uuid.v4)()}-${context}.json`);
try {
await import_promises2.default.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
var import_path2 = __toESM(require("path"), 1);
var import_url = __toESM(require("url"), 1);
var import_dotenv = __toESM(require("dotenv"), 1);
var import_meta = {};
import_dotenv.default.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(import_path2.default.join(cwd, "ui-coverage-scenario.config.json")));
};
var buildYamlSettings = () => {
return cleanUndefined(loadFromYaml(import_path2.default.join(cwd, "ui-coverage-scenario.config.yaml")));
};
var buildDefaultSettings = () => {
let htmlReportTemplateFile;
try {
htmlReportTemplateFile = import_path2.default.join(
import_path2.default.dirname(import_url.default.fileURLToPath(import_meta.url)),
"reports/templates/index.html"
);
} catch {
htmlReportTemplateFile = import_path2.default.join(cwd, "src/reports/templates/index.html");
}
return {
apps: [],
resultsDir: import_path2.default.join(cwd, "coverage-results"),
historyFile: import_path2.default.join(cwd, "coverage-history.json"),
historyRetentionLimit: 30,
htmlReportFile: import_path2.default.join(cwd, "index.html"),
jsonReportFile: import_path2.default.join(cwd, "coverage-report.json"),
htmlReportTemplateFile
};
};
// src/config/core.ts
var getSettings = () => {
const defaultSettings = buildDefaultSettings();
return {
...defaultSettings,
...buildYamlSettings(),
...buildJsonSettings(),
...buildEnvSettings(),
htmlReportTemplateFile: defaultSettings.htmlReportTemplateFile
};
};
// src/tracker/core.ts
var logger4 = getLogger("UI_COVERAGE_TRACKER");
var UICoverageTracker = class {
constructor({ app, settings }) {
this.app = app;
this.settings = settings || getSettings();
this.storage = new UICoverageTrackerStorage({ settings: this.settings });
this.scenario = null;
}
startScenario({ url: url2, name }) {
this.scenario = { url: url2, app: this.app, name };
}
async endScenario() {
if (this.scenario) {
await this.storage.saveScenarioResult(this.scenario);
}
this.scenario = null;
}
/**
* @deprecated Method is deprecated, use `trackElement` instead.
*/
async trackCoverage(props) {
logger4.warning("Method trackCoverage() is deprecated. Use trackElement() instead.");
await this.trackElement(props);
}
async trackPage({ url: url2, page, priority }) {
if (!this.scenario) {
logger4.warning("No active scenario. Did you forget to call startScenario? Calling: trackPage");
return;
}
await this.storage.savePageResult({
app: this.app,
url: url2,
page,
priority,
scenario: this.scenario.name
});
}
async trackElement({ selector, actionType, selectorType }) {
if (!this.scenario) {
logger4.warning("No active scenario. Did you forget to call startScenario? Calling: trackElement");
return;
}
await this.storage.saveElementResult({
app: this.app,
scenario: this.scenario.name,
selector,
timestamp: Date.now(),
actionType,
selectorType
});
}
async trackTransition({ toPage, fromPage }) {
if (!this.scenario) {
logger4.warning("No active scenario. Did you forget to call startScenario? Calling: trackTransition");
return;
}
await this.storage.saveTransitionResult({
app: this.app,
toPage,
scenario: this.scenario.name,
fromPage
});
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ActionType,
SelectorType,
UICoverageTracker
});
//# sourceMappingURL=index.cjs.map