@swedevtools/livedoc-vitest 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/LICENSE +20 -20
- package/README.md +160 -95
- package/dist/{RuleContext-BZhuy-zS.d.cts → RuleContext-DQ8o_n1D.d.ts} +62 -62
- package/dist/globals.d.ts +99 -99
- package/dist/{index-Blmp569T.d.cts → index-sbV15ohX.d.ts} +21 -2
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1062 -159
- package/dist/reporter/index.d.ts +2 -2
- package/dist/reporter/index.js +629 -84
- package/package.json +14 -12
- package/tools/livedoc-setup.mjs +172 -164
- package/tools/skills/SKILL.md +339 -244
- package/tools/skills/VALIDATION.md +37 -29
- package/tools/skills/examples/routing.md +75 -60
- package/tools/skills/resources/anti-patterns.md +19 -0
- package/tools/skills/resources/bdd-features.md +231 -231
- package/tools/skills/resources/partial-testing.md +77 -0
- package/tools/skills/resources/playwright.md +148 -148
- package/tools/skills/resources/reporter-config.md +213 -163
- package/tools/skills/resources/specifications.md +159 -159
- package/tools/skills/resources/test-strategy.md +103 -0
- package/tools/skills/resources/web-testing.md +62 -0
- package/dist/RuleContext-BZhuy-zS.d.ts +0 -206
- package/dist/globals.cjs +0 -2
- package/dist/globals.d.cts +0 -104
- package/dist/index-CysiWbtk.d.ts +0 -687
- package/dist/index.cjs +0 -10024
- package/dist/index.d.cts +0 -291
- package/dist/playwright/index.cjs +0 -103
- package/dist/playwright/index.d.cts +0 -129
- package/dist/reporter/index.cjs +0 -8676
- package/dist/reporter/index.d.cts +0 -7
- package/dist/setup.cjs +0 -14
- package/dist/setup.d.cts +0 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import chalk3, { Chalk } from 'chalk';
|
|
2
2
|
import { afterAll, it, describe, beforeAll, TestRunner } from 'vitest';
|
|
3
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
3
4
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import * as
|
|
5
|
-
import { dirname, resolve } from 'path';
|
|
6
|
-
import { existsSync, unlinkSync, appendFileSync, mkdirSync, writeFileSync, promises } from 'fs';
|
|
5
|
+
import * as path2 from 'path';
|
|
6
|
+
import path2__default, { dirname, resolve } from 'path';
|
|
7
|
+
import { existsSync, unlinkSync, appendFileSync, mkdirSync, writeFileSync, promises, statSync, readFileSync } from 'fs';
|
|
7
8
|
import CliTable3 from 'cli-table3';
|
|
8
9
|
import * as diff from 'diff';
|
|
9
10
|
import wordwrap from 'wordwrap';
|
|
@@ -1565,10 +1566,12 @@ var FilterOptions = class {
|
|
|
1565
1566
|
var PublishOptions = class {
|
|
1566
1567
|
/** Server URL, e.g., 'http://localhost:3100' */
|
|
1567
1568
|
server = "http://localhost:3100";
|
|
1568
|
-
/** Project name (defaults to '
|
|
1569
|
-
project = "
|
|
1569
|
+
/** Project name (defaults to 'livedoc') */
|
|
1570
|
+
project = "livedoc";
|
|
1570
1571
|
/** Environment name (defaults to 'local') */
|
|
1571
1572
|
environment = "local";
|
|
1573
|
+
/** Whether the invocation reports the complete test inventory or a focused subset. */
|
|
1574
|
+
runType = "full";
|
|
1572
1575
|
/** Whether publishing is enabled */
|
|
1573
1576
|
enabled = false;
|
|
1574
1577
|
};
|
|
@@ -1682,6 +1685,7 @@ var currentStep = null;
|
|
|
1682
1685
|
var scenarioCount = 0;
|
|
1683
1686
|
var scenarioId = 0;
|
|
1684
1687
|
var currentSpecification = null;
|
|
1688
|
+
var declarationContextStorage = new AsyncLocalStorage();
|
|
1685
1689
|
var specificationRegistry = [];
|
|
1686
1690
|
var afterBackgroundFnMap = /* @__PURE__ */ new Map();
|
|
1687
1691
|
var backgroundStepsMap = /* @__PURE__ */ new Map();
|
|
@@ -1690,10 +1694,76 @@ var backgroundStepsComplete = false;
|
|
|
1690
1694
|
var scenarioStartHooks = [];
|
|
1691
1695
|
var scenarioEndHooks = [];
|
|
1692
1696
|
var isPendingContext = false;
|
|
1697
|
+
var currentFilterOmissionReason;
|
|
1698
|
+
var isIncludedContext = false;
|
|
1693
1699
|
var isFilteredContext = false;
|
|
1694
1700
|
var isDynamicExecution = false;
|
|
1695
1701
|
var capturedThrownException = null;
|
|
1696
1702
|
var resultsFileWritten = false;
|
|
1703
|
+
function createDefaultOptions() {
|
|
1704
|
+
const options = new LiveDocOptions();
|
|
1705
|
+
options.rules.singleGivenWhenThen = "enabled" /* enabled */;
|
|
1706
|
+
options.rules.backgroundMustOnlyIncludeGiven = "enabled" /* enabled */;
|
|
1707
|
+
options.rules.enforceTitle = "enabled" /* enabled */;
|
|
1708
|
+
options.rules.enforceUsingGivenOverBefore = "warning" /* warning */;
|
|
1709
|
+
options.rules.mustIncludeGiven = "warning" /* warning */;
|
|
1710
|
+
options.rules.mustIncludeWhen = "warning" /* warning */;
|
|
1711
|
+
options.rules.mustIncludeThen = "warning" /* warning */;
|
|
1712
|
+
return options;
|
|
1713
|
+
}
|
|
1714
|
+
function assertDeclarationParent(type, title, expectedParent, missingParentMessage, filename) {
|
|
1715
|
+
const declarationTitle = title.trim().split(/\r?\n/, 1)[0].trim();
|
|
1716
|
+
const currentDeclarationContext = declarationContextStorage.getStore();
|
|
1717
|
+
if (currentDeclarationContext?.type === expectedParent) {
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
if (currentDeclarationContext) {
|
|
1721
|
+
throw new ParserException(
|
|
1722
|
+
`Invalid nesting: ${type} "${declarationTitle}" cannot be declared within ${currentDeclarationContext.type} "${currentDeclarationContext.title}".`,
|
|
1723
|
+
declarationTitle,
|
|
1724
|
+
filename
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
if (expectedParent !== null) {
|
|
1728
|
+
throw new ParserException(missingParentMessage, title, filename);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
function captureParserException(error) {
|
|
1732
|
+
if (!isDynamicExecution || !(error instanceof ParserException)) {
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
capturedThrownException = {
|
|
1736
|
+
type: "ParserException",
|
|
1737
|
+
message: error.description,
|
|
1738
|
+
data: {
|
|
1739
|
+
description: error.description,
|
|
1740
|
+
title: error.title,
|
|
1741
|
+
filename: error.filename
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
function withDeclarationContext(context, fn) {
|
|
1746
|
+
return declarationContextStorage.run(context, fn);
|
|
1747
|
+
}
|
|
1748
|
+
async function withDeclarationContextAsync(context, fn) {
|
|
1749
|
+
return await declarationContextStorage.run(context, fn);
|
|
1750
|
+
}
|
|
1751
|
+
function getStepDeclarationType(stepType) {
|
|
1752
|
+
switch (stepType) {
|
|
1753
|
+
case "given":
|
|
1754
|
+
return "Given";
|
|
1755
|
+
case "when":
|
|
1756
|
+
return "When";
|
|
1757
|
+
case "then":
|
|
1758
|
+
return "Then";
|
|
1759
|
+
case "and":
|
|
1760
|
+
return "And";
|
|
1761
|
+
case "but":
|
|
1762
|
+
return "But";
|
|
1763
|
+
default:
|
|
1764
|
+
throw new Error(`Unsupported LiveDoc step type: ${stepType}`);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1697
1767
|
var __filename_esm = fileURLToPath(import.meta.url);
|
|
1698
1768
|
dirname(__filename_esm);
|
|
1699
1769
|
var dynamicResultsFile = process.env.LIVEDOC_DYNAMIC_RESULTS_FILE;
|
|
@@ -1704,11 +1774,13 @@ if (dynamicResultsFile) {
|
|
|
1704
1774
|
return;
|
|
1705
1775
|
}
|
|
1706
1776
|
try {
|
|
1777
|
+
const outputFeatures = pruneFilteredFeatures(featureRegistry);
|
|
1778
|
+
const outputSpecifications = pruneFilteredSpecifications(specificationRegistry);
|
|
1707
1779
|
const fs2 = __require("fs");
|
|
1708
1780
|
const results = {
|
|
1709
|
-
features:
|
|
1781
|
+
features: outputFeatures.map((f) => f.toJSON()),
|
|
1710
1782
|
suites: suiteRegistry.map((s) => s.toJSON()),
|
|
1711
|
-
specifications:
|
|
1783
|
+
specifications: outputSpecifications.map((s) => s.toJSON())
|
|
1712
1784
|
};
|
|
1713
1785
|
if (capturedThrownException) {
|
|
1714
1786
|
results.thrownException = capturedThrownException;
|
|
@@ -1725,15 +1797,8 @@ if (dynamicResultsFile) {
|
|
|
1725
1797
|
});
|
|
1726
1798
|
}
|
|
1727
1799
|
var livedoc = {
|
|
1728
|
-
options:
|
|
1729
|
-
};
|
|
1730
|
-
livedoc.options.rules.singleGivenWhenThen = "enabled" /* enabled */;
|
|
1731
|
-
livedoc.options.rules.backgroundMustOnlyIncludeGiven = "enabled" /* enabled */;
|
|
1732
|
-
livedoc.options.rules.enforceTitle = "enabled" /* enabled */;
|
|
1733
|
-
livedoc.options.rules.enforceUsingGivenOverBefore = "warning" /* warning */;
|
|
1734
|
-
livedoc.options.rules.mustIncludeGiven = "warning" /* warning */;
|
|
1735
|
-
livedoc.options.rules.mustIncludeWhen = "warning" /* warning */;
|
|
1736
|
-
livedoc.options.rules.mustIncludeThen = "warning" /* warning */;
|
|
1800
|
+
options: createDefaultOptions()
|
|
1801
|
+
};
|
|
1737
1802
|
var displayedViolations = {};
|
|
1738
1803
|
function markedAsExcluded(tags) {
|
|
1739
1804
|
if (tags.length === 0 || !livedoc.options.filters.exclude) {
|
|
@@ -1757,14 +1822,189 @@ function markedAsIncluded(tags) {
|
|
|
1757
1822
|
}
|
|
1758
1823
|
return false;
|
|
1759
1824
|
}
|
|
1760
|
-
function
|
|
1761
|
-
return
|
|
1825
|
+
function hasIncludeFilter() {
|
|
1826
|
+
return (livedoc.options.filters.include?.length ?? 0) > 0;
|
|
1762
1827
|
}
|
|
1763
|
-
function
|
|
1764
|
-
|
|
1765
|
-
|
|
1828
|
+
function decideFilter(tags, inheritedIncluded, inheritedFilteredOut, allowIncludeMiss) {
|
|
1829
|
+
const directlyIncluded = markedAsIncluded(tags);
|
|
1830
|
+
const directlyExcluded = markedAsExcluded(tags);
|
|
1831
|
+
const included = inheritedIncluded || directlyIncluded;
|
|
1832
|
+
const isConflict = directlyIncluded && directlyExcluded;
|
|
1833
|
+
if (inheritedFilteredOut) {
|
|
1834
|
+
return {
|
|
1835
|
+
filteredOut: true,
|
|
1836
|
+
included,
|
|
1837
|
+
pendingConflict: false,
|
|
1838
|
+
reason: "parent-filtered"
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
if (isConflict && !!livedoc.options.filters.showFilterConflicts) {
|
|
1842
|
+
return {
|
|
1843
|
+
filteredOut: false,
|
|
1844
|
+
included: true,
|
|
1845
|
+
pendingConflict: true
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
if (directlyExcluded) {
|
|
1849
|
+
return {
|
|
1850
|
+
filteredOut: true,
|
|
1851
|
+
included,
|
|
1852
|
+
pendingConflict: false,
|
|
1853
|
+
reason: "exclude"
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
if (allowIncludeMiss && hasIncludeFilter() && !included) {
|
|
1857
|
+
return {
|
|
1858
|
+
filteredOut: true,
|
|
1859
|
+
included: false,
|
|
1860
|
+
pendingConflict: false,
|
|
1861
|
+
reason: "include-miss"
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
return {
|
|
1865
|
+
filteredOut: false,
|
|
1866
|
+
included,
|
|
1867
|
+
pendingConflict: false
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
function markFilteredOut(target, decision) {
|
|
1871
|
+
if (!decision.filteredOut) {
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
const mutable = target;
|
|
1875
|
+
mutable.filteredOut = true;
|
|
1876
|
+
mutable.filterOmissionReason = decision.reason ?? "parent-filtered";
|
|
1877
|
+
}
|
|
1878
|
+
function shouldSkipForFilterDecision(decision, explicitPending, inheritedPending = false) {
|
|
1879
|
+
return !!explicitPending || inheritedPending || decision.filteredOut || decision.pendingConflict;
|
|
1880
|
+
}
|
|
1881
|
+
function filterMeta(reason) {
|
|
1882
|
+
if (!reason) {
|
|
1883
|
+
return void 0;
|
|
1884
|
+
}
|
|
1885
|
+
return {
|
|
1886
|
+
filteredOut: true,
|
|
1887
|
+
reason
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1890
|
+
function isFilteredOutModel(value) {
|
|
1891
|
+
return !!value?.filteredOut;
|
|
1892
|
+
}
|
|
1893
|
+
function pruneFilteredFeatures(features) {
|
|
1894
|
+
return features.map((feature3) => pruneFeatureForOutput(feature3)).filter((feature3) => feature3 !== null);
|
|
1895
|
+
}
|
|
1896
|
+
function pruneFeatureForOutput(feature3) {
|
|
1897
|
+
if (isFilteredOutModel(feature3)) {
|
|
1898
|
+
return null;
|
|
1899
|
+
}
|
|
1900
|
+
feature3.scenarios = feature3.scenarios.map((scenario3) => pruneScenarioForOutput(scenario3)).filter((scenario3) => scenario3 !== null);
|
|
1901
|
+
if (feature3.scenarios.length === 0) {
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
recalculateFeatureStatistics(feature3);
|
|
1905
|
+
return feature3;
|
|
1906
|
+
}
|
|
1907
|
+
function pruneScenarioForOutput(scenario3) {
|
|
1908
|
+
if (isFilteredOutModel(scenario3)) {
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
if (scenario3 instanceof ScenarioOutline) {
|
|
1912
|
+
scenario3.examples = scenario3.examples.filter((example) => !isFilteredOutModel(example));
|
|
1913
|
+
if (scenario3.examples.length === 0) {
|
|
1914
|
+
return null;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
return scenario3;
|
|
1918
|
+
}
|
|
1919
|
+
function pruneFilteredSpecifications(specifications) {
|
|
1920
|
+
return specifications.map((specification3) => pruneSpecificationForOutput(specification3)).filter((specification3) => specification3 !== null);
|
|
1921
|
+
}
|
|
1922
|
+
function pruneSpecificationForOutput(specification3) {
|
|
1923
|
+
if (isFilteredOutModel(specification3)) {
|
|
1924
|
+
return null;
|
|
1925
|
+
}
|
|
1926
|
+
specification3.rules = specification3.rules.map((rule3) => pruneRuleForOutput(rule3)).filter((rule3) => rule3 !== null);
|
|
1927
|
+
if (specification3.rules.length === 0) {
|
|
1928
|
+
return null;
|
|
1929
|
+
}
|
|
1930
|
+
recalculateSpecificationStatistics(specification3);
|
|
1931
|
+
return specification3;
|
|
1932
|
+
}
|
|
1933
|
+
function pruneRuleForOutput(rule3) {
|
|
1934
|
+
if (isFilteredOutModel(rule3)) {
|
|
1935
|
+
return null;
|
|
1936
|
+
}
|
|
1937
|
+
if (rule3 instanceof RuleOutline) {
|
|
1938
|
+
rule3.examples = rule3.examples.filter((example) => !isFilteredOutModel(example));
|
|
1939
|
+
if (rule3.examples.length === 0) {
|
|
1940
|
+
return null;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
return rule3;
|
|
1944
|
+
}
|
|
1945
|
+
function resetStatistics(suite) {
|
|
1946
|
+
suite.statistics = new Statistics(suite);
|
|
1947
|
+
}
|
|
1948
|
+
function recalculateFeatureStatistics(feature3) {
|
|
1949
|
+
resetStatistics(feature3);
|
|
1950
|
+
if (feature3.background && !isFilteredOutModel(feature3.background)) {
|
|
1951
|
+
resetStatistics(feature3.background);
|
|
1952
|
+
for (const step of feature3.background.steps) {
|
|
1953
|
+
if (!isFilteredOutModel(step)) {
|
|
1954
|
+
feature3.background.statistics.updateStats(step.status, step.duration);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
for (const scenario3 of feature3.scenarios) {
|
|
1959
|
+
recalculateScenarioStatistics(scenario3);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
function recalculateScenarioStatistics(scenario3) {
|
|
1963
|
+
resetStatistics(scenario3);
|
|
1964
|
+
if (scenario3 instanceof ScenarioOutline) {
|
|
1965
|
+
for (const example of scenario3.examples) {
|
|
1966
|
+
resetStatistics(example);
|
|
1967
|
+
for (const step of example.steps) {
|
|
1968
|
+
if (!isFilteredOutModel(step)) {
|
|
1969
|
+
example.statistics.updateStats(step.status, step.duration);
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
for (const step of scenario3.steps) {
|
|
1976
|
+
if (!isFilteredOutModel(step)) {
|
|
1977
|
+
scenario3.statistics.updateStats(step.status, step.duration);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
function recalculateSpecificationStatistics(specification3) {
|
|
1982
|
+
resetStatistics(specification3);
|
|
1983
|
+
for (const rule3 of specification3.rules) {
|
|
1984
|
+
if (rule3 instanceof RuleOutline) {
|
|
1985
|
+
recalculateRuleOutlineStatus(rule3);
|
|
1986
|
+
for (const example of rule3.examples) {
|
|
1987
|
+
specification3.statistics.updateStats(example.status, example.executionTime);
|
|
1988
|
+
}
|
|
1989
|
+
} else {
|
|
1990
|
+
specification3.statistics.updateStats(rule3.status, rule3.executionTime);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
function recalculateRuleOutlineStatus(ruleOutline3) {
|
|
1995
|
+
if (ruleOutline3.examples.length === 0) {
|
|
1996
|
+
ruleOutline3.status = "unknown" /* unknown */;
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
if (ruleOutline3.examples.some((example) => example.status === "fail" /* fail */)) {
|
|
2000
|
+
ruleOutline3.status = "fail" /* fail */;
|
|
2001
|
+
} else if (ruleOutline3.examples.every((example) => example.status === "pass" /* pass */)) {
|
|
2002
|
+
ruleOutline3.status = "pass" /* pass */;
|
|
2003
|
+
} else if (ruleOutline3.examples.every((example) => example.status === "pending" /* pending */)) {
|
|
2004
|
+
ruleOutline3.status = "pending" /* pending */;
|
|
2005
|
+
} else {
|
|
2006
|
+
ruleOutline3.status = "pass" /* pass */;
|
|
1766
2007
|
}
|
|
1767
|
-
return markedAsIncluded(tags) && (!markedAsExcluded(tags) || !!livedoc.options.filters.showFilterConflicts);
|
|
1768
2008
|
}
|
|
1769
2009
|
function displayRuleViolation(violation, filename) {
|
|
1770
2010
|
if (displayedViolations[violation.errorId]) {
|
|
@@ -1800,6 +2040,7 @@ function displayWarnings(filename) {
|
|
|
1800
2040
|
}
|
|
1801
2041
|
function featureImpl(title, fn, opts = {}) {
|
|
1802
2042
|
const filename = getFilenameFromStack(3);
|
|
2043
|
+
assertDeclarationParent("Feature", title, null, "", filename);
|
|
1803
2044
|
if (fn.constructor.name === "AsyncFunction") {
|
|
1804
2045
|
throw new ParserException(`The async keyword is not supported for Feature`, title, filename);
|
|
1805
2046
|
}
|
|
@@ -1811,23 +2052,39 @@ function featureImpl(title, fn, opts = {}) {
|
|
|
1811
2052
|
thisFeature.generateId(thisFeature);
|
|
1812
2053
|
thisFeature.validateIdUniqueness(thisFeature.id, featureRegistry);
|
|
1813
2054
|
featureRegistry.push(thisFeature);
|
|
1814
|
-
const
|
|
1815
|
-
|
|
2055
|
+
const filterDecision = decideFilter(thisFeature.tags, false, false, false);
|
|
2056
|
+
markFilteredOut(thisFeature, filterDecision);
|
|
2057
|
+
const shouldSkip = shouldSkipForFilterDecision(filterDecision, opts.pending);
|
|
2058
|
+
const shouldOnlyRun = opts.isOnly;
|
|
1816
2059
|
const describeFunc = shouldSkip ? describe.skip : shouldOnlyRun ? describe.only : describe;
|
|
1817
2060
|
describeFunc(thisFeature.displayTitle, () => {
|
|
1818
2061
|
currentFeature = thisFeature;
|
|
1819
2062
|
const previousPendingContext = isPendingContext;
|
|
1820
2063
|
const previousFilteredContext = isFilteredContext;
|
|
2064
|
+
const previousFilterOmissionReason = currentFilterOmissionReason;
|
|
2065
|
+
const previousIncludedContext = isIncludedContext;
|
|
2066
|
+
if (filterDecision.included) {
|
|
2067
|
+
isIncludedContext = true;
|
|
2068
|
+
}
|
|
1821
2069
|
if (shouldSkip) {
|
|
1822
2070
|
isPendingContext = true;
|
|
2071
|
+
if (filterDecision.filteredOut) {
|
|
2072
|
+
isFilteredContext = true;
|
|
2073
|
+
currentFilterOmissionReason = filterDecision.reason ?? "parent-filtered";
|
|
2074
|
+
}
|
|
1823
2075
|
}
|
|
1824
2076
|
const ctx = {
|
|
1825
2077
|
get feature() {
|
|
1826
2078
|
return thisFeature?.getFeatureContext();
|
|
1827
2079
|
}
|
|
1828
2080
|
};
|
|
1829
|
-
|
|
2081
|
+
withDeclarationContext(
|
|
2082
|
+
{ type: "Feature", title: thisFeature.title },
|
|
2083
|
+
() => fn(ctx)
|
|
2084
|
+
);
|
|
1830
2085
|
isPendingContext = previousPendingContext;
|
|
2086
|
+
currentFilterOmissionReason = previousFilterOmissionReason;
|
|
2087
|
+
isIncludedContext = previousIncludedContext;
|
|
1831
2088
|
isFilteredContext = previousFilteredContext;
|
|
1832
2089
|
});
|
|
1833
2090
|
}
|
|
@@ -1845,29 +2102,38 @@ var feature = Object.assign(
|
|
|
1845
2102
|
}
|
|
1846
2103
|
);
|
|
1847
2104
|
function scenarioImpl(title, fn, opts = {}) {
|
|
2105
|
+
const filename = getFilenameFromStack(3);
|
|
2106
|
+
assertDeclarationParent("Scenario", title, "Feature", "Scenario must be within a feature.", filename);
|
|
1848
2107
|
if (!currentFeature) {
|
|
1849
|
-
throw new ParserException("Scenario must be within a feature.", title,
|
|
2108
|
+
throw new ParserException("Scenario must be within a feature.", title, filename);
|
|
1850
2109
|
}
|
|
1851
|
-
const filename = getFilenameFromStack(3);
|
|
1852
2110
|
if (fn.constructor.name === "AsyncFunction") {
|
|
1853
2111
|
throw new ParserException(`The async keyword is not supported for Scenario`, title, filename);
|
|
1854
2112
|
}
|
|
1855
2113
|
const scenarioModel = parser.addScenario(currentFeature, title);
|
|
1856
2114
|
scenarioCount++;
|
|
1857
2115
|
const thisScenarioId = scenarioCount;
|
|
1858
|
-
const
|
|
1859
|
-
|
|
1860
|
-
const
|
|
2116
|
+
const filterDecision = decideFilter(scenarioModel.tags, isIncludedContext, isFilteredContext, true);
|
|
2117
|
+
markFilteredOut(scenarioModel, filterDecision);
|
|
2118
|
+
const shouldSkip = shouldSkipForFilterDecision(filterDecision, opts.pending, isPendingContext);
|
|
2119
|
+
const shouldOnlyRun = opts.isOnly;
|
|
2120
|
+
const inheritedFilterOmissionReason = currentFilterOmissionReason;
|
|
1861
2121
|
const describeFunc = shouldSkip ? describe.skip : shouldOnlyRun ? describe.only : describe;
|
|
1862
2122
|
describeFunc(scenarioModel.displayTitle, () => {
|
|
1863
2123
|
const previousScenario = currentScenario;
|
|
1864
2124
|
currentScenario = scenarioModel;
|
|
1865
2125
|
const previousPendingContext = isPendingContext;
|
|
2126
|
+
const previousFilterOmissionReason = currentFilterOmissionReason;
|
|
2127
|
+
const previousIncludedContext = isIncludedContext;
|
|
2128
|
+
if (filterDecision.included) {
|
|
2129
|
+
isIncludedContext = true;
|
|
2130
|
+
}
|
|
1866
2131
|
const previousFilteredContext = isFilteredContext;
|
|
1867
2132
|
if (shouldSkip) {
|
|
1868
2133
|
isPendingContext = true;
|
|
1869
|
-
if (
|
|
2134
|
+
if (filterDecision.filteredOut) {
|
|
1870
2135
|
isFilteredContext = true;
|
|
2136
|
+
currentFilterOmissionReason = filterDecision.reason ?? inheritedFilterOmissionReason ?? "parent-filtered";
|
|
1871
2137
|
}
|
|
1872
2138
|
}
|
|
1873
2139
|
beforeAll(async () => {
|
|
@@ -1913,8 +2179,13 @@ function scenarioImpl(title, fn, opts = {}) {
|
|
|
1913
2179
|
return scenarioModel.getScenarioContext();
|
|
1914
2180
|
}
|
|
1915
2181
|
};
|
|
1916
|
-
|
|
2182
|
+
withDeclarationContext(
|
|
2183
|
+
{ type: "Scenario", title: scenarioModel.title },
|
|
2184
|
+
() => fn(ctx)
|
|
2185
|
+
);
|
|
1917
2186
|
isPendingContext = previousPendingContext;
|
|
2187
|
+
currentFilterOmissionReason = previousFilterOmissionReason;
|
|
2188
|
+
isIncludedContext = previousIncludedContext;
|
|
1918
2189
|
isFilteredContext = previousFilteredContext;
|
|
1919
2190
|
currentScenario = previousScenario;
|
|
1920
2191
|
});
|
|
@@ -1933,10 +2204,11 @@ var scenario = Object.assign(
|
|
|
1933
2204
|
}
|
|
1934
2205
|
);
|
|
1935
2206
|
function backgroundImpl(title, fn, opts = {}) {
|
|
2207
|
+
const filename = getFilenameFromStack(3);
|
|
2208
|
+
assertDeclarationParent("Background", title, "Feature", "Background must be within a feature.", filename);
|
|
1936
2209
|
if (!currentFeature) {
|
|
1937
|
-
throw new ParserException("Background must be within a feature.", title,
|
|
2210
|
+
throw new ParserException("Background must be within a feature.", title, filename);
|
|
1938
2211
|
}
|
|
1939
|
-
const filename = getFilenameFromStack(3);
|
|
1940
2212
|
if (fn.constructor.name === "AsyncFunction") {
|
|
1941
2213
|
throw new ParserException(`The async keyword is not supported for Background`, title, filename);
|
|
1942
2214
|
}
|
|
@@ -1963,7 +2235,10 @@ function backgroundImpl(title, fn, opts = {}) {
|
|
|
1963
2235
|
}
|
|
1964
2236
|
}
|
|
1965
2237
|
};
|
|
1966
|
-
|
|
2238
|
+
withDeclarationContext(
|
|
2239
|
+
{ type: "Background", title: backgroundModel.title },
|
|
2240
|
+
() => fn(ctx)
|
|
2241
|
+
);
|
|
1967
2242
|
currentBackground = previousBackground;
|
|
1968
2243
|
});
|
|
1969
2244
|
}
|
|
@@ -1987,33 +2262,49 @@ function onScenarioEnd(fn) {
|
|
|
1987
2262
|
scenarioEndHooks.push(fn);
|
|
1988
2263
|
}
|
|
1989
2264
|
function scenarioOutlineImpl(title, fn, opts = {}) {
|
|
2265
|
+
const filename = getFilenameFromStack(3);
|
|
2266
|
+
assertDeclarationParent(
|
|
2267
|
+
"Scenario Outline",
|
|
2268
|
+
title,
|
|
2269
|
+
"Feature",
|
|
2270
|
+
"Scenario Outline must be within a feature.",
|
|
2271
|
+
filename
|
|
2272
|
+
);
|
|
1990
2273
|
if (!currentFeature) {
|
|
1991
|
-
throw new ParserException("Scenario Outline must be within a feature.", title,
|
|
2274
|
+
throw new ParserException("Scenario Outline must be within a feature.", title, filename);
|
|
1992
2275
|
}
|
|
1993
|
-
const filename = getFilenameFromStack(3);
|
|
1994
2276
|
if (fn.constructor.name === "AsyncFunction") {
|
|
1995
2277
|
throw new ParserException(`The async keyword is not supported for Scenario Outline`, title, filename);
|
|
1996
2278
|
}
|
|
1997
2279
|
const scenarioOutlineModel = parser.addScenarioOutline(currentFeature, title);
|
|
1998
|
-
const
|
|
1999
|
-
|
|
2000
|
-
const
|
|
2280
|
+
const filterDecision = decideFilter(scenarioOutlineModel.tags, isIncludedContext, isFilteredContext, true);
|
|
2281
|
+
markFilteredOut(scenarioOutlineModel, filterDecision);
|
|
2282
|
+
const shouldSkip = shouldSkipForFilterDecision(filterDecision, opts.pending, isPendingContext);
|
|
2283
|
+
const shouldOnlyRun = opts.isOnly;
|
|
2284
|
+
const inheritedFilterOmissionReason = currentFilterOmissionReason;
|
|
2001
2285
|
const outlineDescribeFunc = shouldSkip ? describe.skip : shouldOnlyRun ? describe.only : describe;
|
|
2002
2286
|
outlineDescribeFunc(`Scenario: ${scenarioOutlineModel.title}`, () => {
|
|
2003
2287
|
for (const example of scenarioOutlineModel.examples) {
|
|
2004
2288
|
scenarioCount++;
|
|
2005
2289
|
const thisScenarioId = scenarioCount;
|
|
2290
|
+
markFilteredOut(example, filterDecision);
|
|
2006
2291
|
const materializedScenarioTitle = materializePlaceholders(scenarioOutlineModel.title, example.exampleRaw ?? example.example ?? {});
|
|
2007
2292
|
const exampleName = `Example ${example.sequence}: ${materializedScenarioTitle}`;
|
|
2008
2293
|
describe(exampleName, () => {
|
|
2009
2294
|
const previousScenario = currentScenario;
|
|
2010
2295
|
currentScenario = example;
|
|
2011
2296
|
const previousPendingContext = isPendingContext;
|
|
2297
|
+
const previousFilterOmissionReason = currentFilterOmissionReason;
|
|
2298
|
+
const previousIncludedContext = isIncludedContext;
|
|
2299
|
+
if (filterDecision.included) {
|
|
2300
|
+
isIncludedContext = true;
|
|
2301
|
+
}
|
|
2012
2302
|
const previousFilteredContext = isFilteredContext;
|
|
2013
2303
|
if (shouldSkip) {
|
|
2014
2304
|
isPendingContext = true;
|
|
2015
|
-
if (
|
|
2305
|
+
if (filterDecision.filteredOut) {
|
|
2016
2306
|
isFilteredContext = true;
|
|
2307
|
+
currentFilterOmissionReason = filterDecision.reason ?? inheritedFilterOmissionReason ?? "parent-filtered";
|
|
2017
2308
|
}
|
|
2018
2309
|
}
|
|
2019
2310
|
beforeAll(async () => {
|
|
@@ -2062,8 +2353,13 @@ function scenarioOutlineImpl(title, fn, opts = {}) {
|
|
|
2062
2353
|
return example.getScenarioContext();
|
|
2063
2354
|
}
|
|
2064
2355
|
};
|
|
2065
|
-
|
|
2356
|
+
withDeclarationContext(
|
|
2357
|
+
{ type: "Scenario Outline", title: scenarioOutlineModel.title },
|
|
2358
|
+
() => fn(ctx)
|
|
2359
|
+
);
|
|
2066
2360
|
isPendingContext = previousPendingContext;
|
|
2361
|
+
currentFilterOmissionReason = previousFilterOmissionReason;
|
|
2362
|
+
isIncludedContext = previousIncludedContext;
|
|
2067
2363
|
isFilteredContext = previousFilteredContext;
|
|
2068
2364
|
currentScenario = previousScenario;
|
|
2069
2365
|
});
|
|
@@ -2085,19 +2381,31 @@ var scenarioOutline = Object.assign(
|
|
|
2085
2381
|
);
|
|
2086
2382
|
function specificationImpl(title, fn, opts = {}) {
|
|
2087
2383
|
const filename = getFilenameFromStack(3);
|
|
2384
|
+
assertDeclarationParent("Specification", title, null, "", filename);
|
|
2088
2385
|
const thisSpecification = parser.createSpecification(title, filename);
|
|
2089
2386
|
currentSpecification = thisSpecification;
|
|
2090
2387
|
thisSpecification.generateId(thisSpecification);
|
|
2091
2388
|
thisSpecification.validateIdUniqueness(thisSpecification.id, specificationRegistry);
|
|
2092
2389
|
specificationRegistry.push(thisSpecification);
|
|
2093
|
-
const
|
|
2094
|
-
|
|
2390
|
+
const filterDecision = decideFilter(thisSpecification.tags, false, false, false);
|
|
2391
|
+
markFilteredOut(thisSpecification, filterDecision);
|
|
2392
|
+
const shouldSkip = shouldSkipForFilterDecision(filterDecision, opts.pending);
|
|
2393
|
+
const shouldOnlyRun = opts.isOnly;
|
|
2095
2394
|
const describeFunc = shouldSkip ? describe.skip : shouldOnlyRun ? describe.only : describe;
|
|
2096
2395
|
describeFunc(thisSpecification.displayTitle, () => {
|
|
2097
2396
|
currentSpecification = thisSpecification;
|
|
2098
2397
|
const previousPendingContext = isPendingContext;
|
|
2099
2398
|
const previousFilteredContext = isFilteredContext;
|
|
2399
|
+
const previousFilterOmissionReason = currentFilterOmissionReason;
|
|
2400
|
+
const previousIncludedContext = isIncludedContext;
|
|
2401
|
+
if (filterDecision.included) {
|
|
2402
|
+
isIncludedContext = true;
|
|
2403
|
+
}
|
|
2100
2404
|
if (shouldSkip) {
|
|
2405
|
+
if (filterDecision.filteredOut) {
|
|
2406
|
+
isFilteredContext = true;
|
|
2407
|
+
currentFilterOmissionReason = filterDecision.reason ?? "parent-filtered";
|
|
2408
|
+
}
|
|
2101
2409
|
isPendingContext = true;
|
|
2102
2410
|
}
|
|
2103
2411
|
const ctx = {
|
|
@@ -2105,8 +2413,13 @@ function specificationImpl(title, fn, opts = {}) {
|
|
|
2105
2413
|
return thisSpecification?.getSpecificationContext();
|
|
2106
2414
|
}
|
|
2107
2415
|
};
|
|
2108
|
-
|
|
2416
|
+
withDeclarationContext(
|
|
2417
|
+
{ type: "Specification", title: thisSpecification.title },
|
|
2418
|
+
() => fn(ctx)
|
|
2419
|
+
);
|
|
2109
2420
|
isPendingContext = previousPendingContext;
|
|
2421
|
+
currentFilterOmissionReason = previousFilterOmissionReason;
|
|
2422
|
+
isIncludedContext = previousIncludedContext;
|
|
2110
2423
|
isFilteredContext = previousFilteredContext;
|
|
2111
2424
|
});
|
|
2112
2425
|
}
|
|
@@ -2124,13 +2437,20 @@ var specification = Object.assign(
|
|
|
2124
2437
|
}
|
|
2125
2438
|
);
|
|
2126
2439
|
function ruleImpl(title, fn, opts = {}) {
|
|
2440
|
+
const filename = getFilenameFromStack(3);
|
|
2441
|
+
assertDeclarationParent("Rule", title, "Specification", "Rule must be within a specification.", filename);
|
|
2127
2442
|
if (!currentSpecification) {
|
|
2128
|
-
throw new ParserException("Rule must be within a specification.", title,
|
|
2443
|
+
throw new ParserException("Rule must be within a specification.", title, filename);
|
|
2129
2444
|
}
|
|
2130
2445
|
const ruleModel = parser.addRule(currentSpecification, title);
|
|
2131
2446
|
const specificationModel = currentSpecification;
|
|
2132
|
-
const
|
|
2133
|
-
|
|
2447
|
+
const filterDecision = decideFilter(ruleModel.tags, isIncludedContext, isFilteredContext, true);
|
|
2448
|
+
markFilteredOut(ruleModel, filterDecision);
|
|
2449
|
+
const shouldSkip = shouldSkipForFilterDecision(filterDecision, opts.pending, isPendingContext);
|
|
2450
|
+
const shouldOnlyRun = opts.isOnly;
|
|
2451
|
+
if (shouldSkip && !filterDecision.filteredOut) {
|
|
2452
|
+
ruleModel.status = "pending" /* pending */;
|
|
2453
|
+
}
|
|
2134
2454
|
const ruleMeta = {
|
|
2135
2455
|
livedoc: {
|
|
2136
2456
|
kind: "rule",
|
|
@@ -2138,7 +2458,8 @@ function ruleImpl(title, fn, opts = {}) {
|
|
|
2138
2458
|
title: ruleModel.title,
|
|
2139
2459
|
description: ruleModel.description ?? "",
|
|
2140
2460
|
tags: ruleModel.tags ?? []
|
|
2141
|
-
}
|
|
2461
|
+
},
|
|
2462
|
+
...filterDecision.filteredOut ? { filter: filterMeta(filterDecision.reason ?? currentFilterOmissionReason ?? "parent-filtered") } : {}
|
|
2142
2463
|
}
|
|
2143
2464
|
};
|
|
2144
2465
|
const ruleHandler = async () => {
|
|
@@ -2152,10 +2473,14 @@ function ruleImpl(title, fn, opts = {}) {
|
|
|
2152
2473
|
};
|
|
2153
2474
|
const startTime = Date.now();
|
|
2154
2475
|
try {
|
|
2155
|
-
await
|
|
2476
|
+
await withDeclarationContextAsync(
|
|
2477
|
+
{ type: "Rule", title: ruleModel.title },
|
|
2478
|
+
() => fn(ctx)
|
|
2479
|
+
);
|
|
2156
2480
|
ruleModel.status = "pass" /* pass */;
|
|
2157
2481
|
ruleModel.executionTime = Date.now() - startTime;
|
|
2158
2482
|
} catch (error) {
|
|
2483
|
+
captureParserException(error);
|
|
2159
2484
|
ruleModel.status = "fail" /* fail */;
|
|
2160
2485
|
ruleModel.executionTime = Date.now() - startTime;
|
|
2161
2486
|
ruleModel.error = error;
|
|
@@ -2190,12 +2515,25 @@ var rule = Object.assign(
|
|
|
2190
2515
|
}
|
|
2191
2516
|
);
|
|
2192
2517
|
function ruleOutlineImpl(title, fn, opts = {}) {
|
|
2518
|
+
const filename = getFilenameFromStack(3);
|
|
2519
|
+
assertDeclarationParent(
|
|
2520
|
+
"Rule Outline",
|
|
2521
|
+
title,
|
|
2522
|
+
"Specification",
|
|
2523
|
+
"Rule Outline must be within a specification.",
|
|
2524
|
+
filename
|
|
2525
|
+
);
|
|
2193
2526
|
if (!currentSpecification) {
|
|
2194
|
-
throw new ParserException("Rule Outline must be within a specification.", title,
|
|
2527
|
+
throw new ParserException("Rule Outline must be within a specification.", title, filename);
|
|
2195
2528
|
}
|
|
2196
2529
|
const ruleOutlineModel = parser.addRuleOutline(currentSpecification, title);
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2530
|
+
const filterDecision = decideFilter(ruleOutlineModel.tags, isIncludedContext, isFilteredContext, true);
|
|
2531
|
+
markFilteredOut(ruleOutlineModel, filterDecision);
|
|
2532
|
+
const shouldSkip = shouldSkipForFilterDecision(filterDecision, opts.pending, isPendingContext);
|
|
2533
|
+
const shouldOnlyRun = opts.isOnly;
|
|
2534
|
+
if (shouldSkip && !filterDecision.filteredOut) {
|
|
2535
|
+
ruleOutlineModel.status = "pending" /* pending */;
|
|
2536
|
+
}
|
|
2199
2537
|
const outlineDescribeFunc = shouldSkip ? describe.skip : shouldOnlyRun ? describe.only : describe;
|
|
2200
2538
|
const specificationModel = currentSpecification;
|
|
2201
2539
|
outlineDescribeFunc(`Rule Outline: ${ruleOutlineModel.title}`, () => {
|
|
@@ -2205,6 +2543,10 @@ function ruleOutlineImpl(title, fn, opts = {}) {
|
|
|
2205
2543
|
example.title = materializedRuleTitle;
|
|
2206
2544
|
example.displayTitle = materializedRuleTitle;
|
|
2207
2545
|
const exampleName = `Example ${example.sequence}: ${materializedRuleTitle}`;
|
|
2546
|
+
markFilteredOut(example, filterDecision);
|
|
2547
|
+
if (shouldSkip && !filterDecision.filteredOut) {
|
|
2548
|
+
example.status = "pending" /* pending */;
|
|
2549
|
+
}
|
|
2208
2550
|
const exampleMeta = {
|
|
2209
2551
|
livedoc: {
|
|
2210
2552
|
kind: "ruleExample",
|
|
@@ -2218,7 +2560,8 @@ function ruleOutlineImpl(title, fn, opts = {}) {
|
|
|
2218
2560
|
values: example.example ?? {},
|
|
2219
2561
|
valuesRaw: example.exampleRaw ?? {}
|
|
2220
2562
|
}
|
|
2221
|
-
}
|
|
2563
|
+
},
|
|
2564
|
+
...filterDecision.filteredOut ? { filter: filterMeta(filterDecision.reason ?? currentFilterOmissionReason ?? "parent-filtered") } : {}
|
|
2222
2565
|
}
|
|
2223
2566
|
};
|
|
2224
2567
|
const exampleHandler = async () => {
|
|
@@ -2235,10 +2578,14 @@ function ruleOutlineImpl(title, fn, opts = {}) {
|
|
|
2235
2578
|
};
|
|
2236
2579
|
const startTime = Date.now();
|
|
2237
2580
|
try {
|
|
2238
|
-
await
|
|
2581
|
+
await withDeclarationContextAsync(
|
|
2582
|
+
{ type: "Rule Outline", title: ruleOutlineModel.title },
|
|
2583
|
+
() => fn(ctx)
|
|
2584
|
+
);
|
|
2239
2585
|
example.status = "pass" /* pass */;
|
|
2240
2586
|
example.executionTime = Date.now() - startTime;
|
|
2241
2587
|
} catch (error) {
|
|
2588
|
+
captureParserException(error);
|
|
2242
2589
|
example.status = "fail" /* fail */;
|
|
2243
2590
|
example.executionTime = Date.now() - startTime;
|
|
2244
2591
|
example.error = error;
|
|
@@ -2254,6 +2601,7 @@ function ruleOutlineImpl(title, fn, opts = {}) {
|
|
|
2254
2601
|
}
|
|
2255
2602
|
currentSuite.task(exampleName, {
|
|
2256
2603
|
meta: exampleMeta,
|
|
2604
|
+
skip: shouldSkip,
|
|
2257
2605
|
handler: exampleHandler
|
|
2258
2606
|
});
|
|
2259
2607
|
}
|
|
@@ -2283,6 +2631,10 @@ function createStepFunction(stepType) {
|
|
|
2283
2631
|
);
|
|
2284
2632
|
}
|
|
2285
2633
|
const stepDefinition = parser.createStep(stepType, title, passedParam);
|
|
2634
|
+
if (isFilteredContext) {
|
|
2635
|
+
stepDefinition.filteredOut = true;
|
|
2636
|
+
stepDefinition.filterOmissionReason = currentFilterOmissionReason ?? "parent-filtered";
|
|
2637
|
+
}
|
|
2286
2638
|
if (currentScenario instanceof ScenarioExample || currentScenario instanceof ScenarioOutline) {
|
|
2287
2639
|
parser.applyPassedParams(stepDefinition);
|
|
2288
2640
|
}
|
|
@@ -2370,7 +2722,8 @@ function createStepFunction(stepType) {
|
|
|
2370
2722
|
}
|
|
2371
2723
|
} : {}
|
|
2372
2724
|
}
|
|
2373
|
-
} : {}
|
|
2725
|
+
} : {},
|
|
2726
|
+
...isFilteredContext ? { filter: filterMeta(currentFilterOmissionReason ?? "parent-filtered") } : {}
|
|
2374
2727
|
}
|
|
2375
2728
|
};
|
|
2376
2729
|
const stepHandler = async () => {
|
|
@@ -2396,10 +2749,10 @@ function createStepFunction(stepType) {
|
|
|
2396
2749
|
return stepDefinition.getStepContext();
|
|
2397
2750
|
}
|
|
2398
2751
|
};
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2752
|
+
await withDeclarationContextAsync(
|
|
2753
|
+
{ type: getStepDeclarationType(stepDefinition.type), title: stepDefinition.title },
|
|
2754
|
+
() => fn(ctx)
|
|
2755
|
+
);
|
|
2403
2756
|
}
|
|
2404
2757
|
} else if (capturedScenario) {
|
|
2405
2758
|
const backgroundItExecuted = capturedFeature ? backgroundItExecutedMap.get(capturedFeature) ?? false : false;
|
|
@@ -2412,20 +2765,23 @@ function createStepFunction(stepType) {
|
|
|
2412
2765
|
parser.applyPassedParams(stepDetail.stepDefinition);
|
|
2413
2766
|
const bgStartTime = Date.now();
|
|
2414
2767
|
try {
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2768
|
+
await withDeclarationContextAsync(
|
|
2769
|
+
{
|
|
2770
|
+
type: getStepDeclarationType(stepDetail.stepDefinition.type),
|
|
2771
|
+
title: stepDetail.stepDefinition.title
|
|
2418
2772
|
},
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2773
|
+
() => stepDetail.func({
|
|
2774
|
+
get feature() {
|
|
2775
|
+
return capturedFeature?.getFeatureContext();
|
|
2776
|
+
},
|
|
2777
|
+
get background() {
|
|
2778
|
+
return capturedFeature?.getBackgroundContext();
|
|
2779
|
+
},
|
|
2780
|
+
get step() {
|
|
2781
|
+
return stepDetail.stepDefinition.getStepContext();
|
|
2782
|
+
}
|
|
2783
|
+
})
|
|
2784
|
+
);
|
|
2429
2785
|
stepDetail.stepDefinition.setStatus("pass" /* pass */, Date.now() - bgStartTime);
|
|
2430
2786
|
} catch (error) {
|
|
2431
2787
|
stepDetail.stepDefinition.setStatus("fail" /* fail */, Date.now() - bgStartTime);
|
|
@@ -2459,10 +2815,10 @@ function createStepFunction(stepType) {
|
|
|
2459
2815
|
return capturedFeature?.getBackgroundContext();
|
|
2460
2816
|
}
|
|
2461
2817
|
};
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2818
|
+
await withDeclarationContextAsync(
|
|
2819
|
+
{ type: getStepDeclarationType(stepDefinition.type), title: stepDefinition.title },
|
|
2820
|
+
() => fn(ctx)
|
|
2821
|
+
);
|
|
2466
2822
|
}
|
|
2467
2823
|
}
|
|
2468
2824
|
const duration = Date.now() - startTime;
|
|
@@ -2471,6 +2827,7 @@ function createStepFunction(stepType) {
|
|
|
2471
2827
|
taskMeta.livedoc.step.attachments = stepDefinition.attachments;
|
|
2472
2828
|
}
|
|
2473
2829
|
} catch (error) {
|
|
2830
|
+
captureParserException(error);
|
|
2474
2831
|
const duration = Date.now() - startTime;
|
|
2475
2832
|
stepDefinition.setStatus("fail" /* fail */, duration);
|
|
2476
2833
|
const exception = new Exception();
|
|
@@ -2499,7 +2856,8 @@ function createStepFunction(stepType) {
|
|
|
2499
2856
|
}
|
|
2500
2857
|
currentSuite.task(testName, {
|
|
2501
2858
|
meta: taskMeta,
|
|
2502
|
-
handler: stepHandler
|
|
2859
|
+
handler: stepHandler,
|
|
2860
|
+
skip: isFilteredContext || void 0
|
|
2503
2861
|
});
|
|
2504
2862
|
};
|
|
2505
2863
|
}
|
|
@@ -2850,9 +3208,9 @@ var LiveDocReporter = class {
|
|
|
2850
3208
|
if (!strs || strs.length === 0)
|
|
2851
3209
|
return "";
|
|
2852
3210
|
if (strs.length === 1)
|
|
2853
|
-
return
|
|
3211
|
+
return path2.dirname(strs[0]);
|
|
2854
3212
|
for (let i = 0; i < strs.length; i++) {
|
|
2855
|
-
strs[i] =
|
|
3213
|
+
strs[i] = path2.dirname(strs[i]);
|
|
2856
3214
|
}
|
|
2857
3215
|
let shortestString = "";
|
|
2858
3216
|
let shortestLength = Number.MAX_SAFE_INTEGER;
|
|
@@ -2890,8 +3248,8 @@ var LiveDocReporter = class {
|
|
|
2890
3248
|
if (rootPath.length === 0 || stripPath === "") {
|
|
2891
3249
|
return "";
|
|
2892
3250
|
} else {
|
|
2893
|
-
let dirPath =
|
|
2894
|
-
if (dirPath.startsWith(
|
|
3251
|
+
let dirPath = path2.parse(stripPath).dir;
|
|
3252
|
+
if (dirPath.startsWith(path2.sep)) {
|
|
2895
3253
|
return dirPath.substr(1);
|
|
2896
3254
|
} else {
|
|
2897
3255
|
return dirPath;
|
|
@@ -3111,6 +3469,9 @@ var LiveDocSpec = class _LiveDocSpec extends LiveDocReporter {
|
|
|
3111
3469
|
}
|
|
3112
3470
|
outputSpecificationDetails(spec) {
|
|
3113
3471
|
this.writeLine(this.formatKeywordTitle("Specification", spec.title, this.colorTheme.keyword, this.colorTheme.featureTitle, 2));
|
|
3472
|
+
if (spec.tags.length > 0) {
|
|
3473
|
+
this.writeLine(this.applyBlockIndent(this.formatTags(spec.tags), 4));
|
|
3474
|
+
}
|
|
3114
3475
|
if (spec.description) {
|
|
3115
3476
|
this.writeLine(this.formatDescription(spec.description, 4, this.colorTheme.featureDescription));
|
|
3116
3477
|
}
|
|
@@ -3120,6 +3481,9 @@ var LiveDocSpec = class _LiveDocSpec extends LiveDocReporter {
|
|
|
3120
3481
|
const status = this.getStatusIndicator(rule3.status);
|
|
3121
3482
|
const title = this.highlight(rule3.title, /<[^>]+>/g, this.colorTheme.valuePlaceholders);
|
|
3122
3483
|
this.writeLine(` ${status} ${this.colorTheme.keyword("Rule Outline:")} ${this.colorTheme.scenarioTitle(title)}`);
|
|
3484
|
+
if (rule3.tags.length > 0) {
|
|
3485
|
+
this.writeLine(this.applyBlockIndent(this.formatTags(rule3.tags), 6));
|
|
3486
|
+
}
|
|
3123
3487
|
if (rule3.description) {
|
|
3124
3488
|
this.writeLine(this.formatDescription(rule3.description, 6, this.colorTheme.scenarioDescription));
|
|
3125
3489
|
}
|
|
@@ -3148,6 +3512,9 @@ var LiveDocSpec = class _LiveDocSpec extends LiveDocReporter {
|
|
|
3148
3512
|
} else {
|
|
3149
3513
|
const status = this.getStatusIndicator(rule3.status);
|
|
3150
3514
|
this.writeLine(` ${status} ${this.colorTheme.keyword("Rule:")} ${this.colorTheme.stepTitle(rule3.title)}`);
|
|
3515
|
+
if (rule3.tags.length > 0) {
|
|
3516
|
+
this.writeLine(this.applyBlockIndent(this.formatTags(rule3.tags), 6));
|
|
3517
|
+
}
|
|
3151
3518
|
}
|
|
3152
3519
|
}
|
|
3153
3520
|
this.writeLine(" ");
|
|
@@ -4460,8 +4827,8 @@ function getErrorMap() {
|
|
|
4460
4827
|
|
|
4461
4828
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
4462
4829
|
var makeIssue = (params) => {
|
|
4463
|
-
const { data, path:
|
|
4464
|
-
const fullPath = [...
|
|
4830
|
+
const { data, path: path4, errorMaps, issueData } = params;
|
|
4831
|
+
const fullPath = [...path4, ...issueData.path || []];
|
|
4465
4832
|
const fullIssue = {
|
|
4466
4833
|
...issueData,
|
|
4467
4834
|
path: fullPath
|
|
@@ -4577,11 +4944,11 @@ var errorUtil;
|
|
|
4577
4944
|
|
|
4578
4945
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
4579
4946
|
var ParseInputLazyPath = class {
|
|
4580
|
-
constructor(parent, value,
|
|
4947
|
+
constructor(parent, value, path4, key) {
|
|
4581
4948
|
this._cachedPath = [];
|
|
4582
4949
|
this.parent = parent;
|
|
4583
4950
|
this.data = value;
|
|
4584
|
-
this._path =
|
|
4951
|
+
this._path = path4;
|
|
4585
4952
|
this._key = key;
|
|
4586
4953
|
}
|
|
4587
4954
|
get path() {
|
|
@@ -8033,9 +8400,9 @@ function simpleHash(str) {
|
|
|
8033
8400
|
return Math.abs(hash).toString(36);
|
|
8034
8401
|
}
|
|
8035
8402
|
function generateStabilityId(params) {
|
|
8036
|
-
const { project, path:
|
|
8403
|
+
const { project, path: path4, title, kind, parentId, keyword, index } = params;
|
|
8037
8404
|
if (!parentId) {
|
|
8038
|
-
return simpleHash(`${project}:${
|
|
8405
|
+
return simpleHash(`${project}:${path4 || ""}:${title}`);
|
|
8039
8406
|
}
|
|
8040
8407
|
if (kind === "Step") {
|
|
8041
8408
|
return `${parentId}:${simpleHash(`${keyword || ""}:${title}:${index ?? 0}`)}`;
|
|
@@ -8101,6 +8468,53 @@ var V1StatisticsSchema = external_exports.object({
|
|
|
8101
8468
|
pending: external_exports.number().int().nonnegative(),
|
|
8102
8469
|
skipped: external_exports.number().int().nonnegative()
|
|
8103
8470
|
});
|
|
8471
|
+
var V1CoverageMetricNameSchema = external_exports.enum(["lines", "branches", "functions", "statements"]);
|
|
8472
|
+
var V1CoverageMetricSchema = external_exports.object({
|
|
8473
|
+
covered: external_exports.number().nonnegative(),
|
|
8474
|
+
total: external_exports.number().nonnegative(),
|
|
8475
|
+
skipped: external_exports.number().nonnegative().optional(),
|
|
8476
|
+
pct: external_exports.number().min(0).max(100).nullable()
|
|
8477
|
+
});
|
|
8478
|
+
var V1CoverageSummarySchema = external_exports.object({
|
|
8479
|
+
lines: V1CoverageMetricSchema.optional(),
|
|
8480
|
+
branches: V1CoverageMetricSchema.optional(),
|
|
8481
|
+
functions: V1CoverageMetricSchema.optional(),
|
|
8482
|
+
statements: V1CoverageMetricSchema.optional()
|
|
8483
|
+
});
|
|
8484
|
+
var V1CoverageProvenanceSchema = external_exports.object({
|
|
8485
|
+
tool: external_exports.string().optional(),
|
|
8486
|
+
format: external_exports.string(),
|
|
8487
|
+
path: external_exports.string().optional(),
|
|
8488
|
+
detected: external_exports.enum(["auto", "configured"]),
|
|
8489
|
+
generatedAt: external_exports.string().optional()
|
|
8490
|
+
});
|
|
8491
|
+
var V1CoverageFileSchema = external_exports.object({
|
|
8492
|
+
path: external_exports.string(),
|
|
8493
|
+
module: external_exports.string().optional(),
|
|
8494
|
+
summary: V1CoverageSummarySchema,
|
|
8495
|
+
detailRef: external_exports.string().optional()
|
|
8496
|
+
});
|
|
8497
|
+
var V1CoverageDiagnosticSchema = external_exports.object({
|
|
8498
|
+
severity: external_exports.enum(["info", "warning", "error"]),
|
|
8499
|
+
code: external_exports.string(),
|
|
8500
|
+
message: external_exports.string(),
|
|
8501
|
+
path: external_exports.string().optional(),
|
|
8502
|
+
details: external_exports.array(external_exports.string()).optional()
|
|
8503
|
+
});
|
|
8504
|
+
var V1CoverageThresholdSchema = external_exports.object({
|
|
8505
|
+
metric: V1CoverageMetricNameSchema,
|
|
8506
|
+
minimum: external_exports.number().min(0).max(100),
|
|
8507
|
+
actual: external_exports.number().min(0).max(100).nullable(),
|
|
8508
|
+
status: external_exports.enum(["passed", "warning"])
|
|
8509
|
+
});
|
|
8510
|
+
var V1CoverageReportSchema = external_exports.object({
|
|
8511
|
+
status: external_exports.enum(["available", "not-collected", "partial", "invalid"]),
|
|
8512
|
+
summary: V1CoverageSummarySchema.optional(),
|
|
8513
|
+
files: external_exports.array(V1CoverageFileSchema).optional(),
|
|
8514
|
+
diagnostics: external_exports.array(V1CoverageDiagnosticSchema).optional(),
|
|
8515
|
+
provenance: V1CoverageProvenanceSchema.optional(),
|
|
8516
|
+
thresholds: external_exports.array(V1CoverageThresholdSchema).optional()
|
|
8517
|
+
});
|
|
8104
8518
|
var V1BaseTestSchema = external_exports.object({
|
|
8105
8519
|
id: external_exports.string(),
|
|
8106
8520
|
kind: external_exports.string(),
|
|
@@ -8164,10 +8578,12 @@ var V1TestCaseSchema = external_exports.object({
|
|
|
8164
8578
|
ruleViolations: external_exports.array(V1RuleViolationSchema).optional()
|
|
8165
8579
|
});
|
|
8166
8580
|
var V1FrameworkSchema = external_exports.string();
|
|
8581
|
+
var V1RunTypeSchema = external_exports.enum(["full", "partial"]);
|
|
8167
8582
|
external_exports.object({
|
|
8168
8583
|
protocolVersion: external_exports.literal("1.0"),
|
|
8169
8584
|
runId: external_exports.string(),
|
|
8170
|
-
|
|
8585
|
+
runType: V1RunTypeSchema.optional(),
|
|
8586
|
+
baselineRunId: external_exports.string().optional(),
|
|
8171
8587
|
project: external_exports.string(),
|
|
8172
8588
|
environment: external_exports.string(),
|
|
8173
8589
|
framework: V1FrameworkSchema,
|
|
@@ -8175,18 +8591,19 @@ external_exports.object({
|
|
|
8175
8591
|
duration: external_exports.number().nonnegative(),
|
|
8176
8592
|
status: V1StatusSchema,
|
|
8177
8593
|
summary: V1StatisticsSchema,
|
|
8178
|
-
documents: external_exports.array(V1TestCaseSchema)
|
|
8594
|
+
documents: external_exports.array(V1TestCaseSchema),
|
|
8595
|
+
coverage: V1CoverageReportSchema.optional()
|
|
8179
8596
|
});
|
|
8180
8597
|
external_exports.object({
|
|
8181
8598
|
project: external_exports.string(),
|
|
8182
8599
|
environment: external_exports.string(),
|
|
8183
8600
|
framework: external_exports.string(),
|
|
8184
|
-
timestamp: external_exports.string().optional()
|
|
8601
|
+
timestamp: external_exports.string().optional(),
|
|
8602
|
+
runType: V1RunTypeSchema.optional()
|
|
8185
8603
|
});
|
|
8186
8604
|
external_exports.object({
|
|
8187
8605
|
protocolVersion: external_exports.literal("1.0"),
|
|
8188
8606
|
runId: external_exports.string(),
|
|
8189
|
-
sessionId: external_exports.string().optional(),
|
|
8190
8607
|
websocketUrl: external_exports.string()
|
|
8191
8608
|
});
|
|
8192
8609
|
external_exports.object({
|
|
@@ -8197,7 +8614,8 @@ external_exports.object({
|
|
|
8197
8614
|
complete: external_exports.object({
|
|
8198
8615
|
status: V1StatusSchema,
|
|
8199
8616
|
duration: external_exports.number().nonnegative(),
|
|
8200
|
-
summary: V1StatisticsSchema.optional()
|
|
8617
|
+
summary: V1StatisticsSchema.optional(),
|
|
8618
|
+
coverage: V1CoverageReportSchema.optional()
|
|
8201
8619
|
}).optional()
|
|
8202
8620
|
});
|
|
8203
8621
|
external_exports.object({
|
|
@@ -8226,31 +8644,17 @@ var V1UpsertOutlineExampleResultsRequestSchema = external_exports.object({
|
|
|
8226
8644
|
external_exports.object({
|
|
8227
8645
|
status: V1StatusSchema,
|
|
8228
8646
|
duration: external_exports.number().nonnegative(),
|
|
8229
|
-
summary: V1StatisticsSchema.optional()
|
|
8230
|
-
|
|
8231
|
-
var V1SessionRunInfoSchema = external_exports.object({
|
|
8232
|
-
runId: external_exports.string(),
|
|
8233
|
-
framework: external_exports.string(),
|
|
8234
|
-
status: V1StatusSchema,
|
|
8235
|
-
timestamp: external_exports.string(),
|
|
8236
|
-
duration: external_exports.number().nonnegative(),
|
|
8237
|
-
summary: V1StatisticsSchema,
|
|
8238
|
-
documentCount: external_exports.number().int().nonnegative()
|
|
8647
|
+
summary: V1StatisticsSchema.optional(),
|
|
8648
|
+
coverage: V1CoverageReportSchema.optional()
|
|
8239
8649
|
});
|
|
8240
8650
|
external_exports.object({
|
|
8241
|
-
|
|
8242
|
-
project: external_exports.string(),
|
|
8243
|
-
environment: external_exports.string(),
|
|
8244
|
-
status: V1StatusSchema,
|
|
8245
|
-
timestamp: external_exports.string(),
|
|
8246
|
-
duration: external_exports.number().nonnegative(),
|
|
8247
|
-
summary: V1StatisticsSchema,
|
|
8248
|
-
runs: external_exports.array(V1SessionRunInfoSchema),
|
|
8249
|
-
documents: external_exports.array(V1TestCaseSchema)
|
|
8651
|
+
coverage: V1CoverageReportSchema
|
|
8250
8652
|
});
|
|
8251
8653
|
var V1WsRunStartedSchema = external_exports.object({
|
|
8252
8654
|
type: external_exports.literal("run:v1:started"),
|
|
8253
8655
|
runId: external_exports.string(),
|
|
8656
|
+
runType: V1RunTypeSchema.optional(),
|
|
8657
|
+
baselineRunId: external_exports.string().optional(),
|
|
8254
8658
|
project: external_exports.string(),
|
|
8255
8659
|
environment: external_exports.string(),
|
|
8256
8660
|
framework: external_exports.string(),
|
|
@@ -8284,15 +8688,13 @@ var V1WsRunCompletedSchema = external_exports.object({
|
|
|
8284
8688
|
runId: external_exports.string(),
|
|
8285
8689
|
status: V1StatusSchema,
|
|
8286
8690
|
duration: external_exports.number().nonnegative(),
|
|
8287
|
-
summary: V1StatisticsSchema
|
|
8691
|
+
summary: V1StatisticsSchema,
|
|
8692
|
+
coverage: V1CoverageReportSchema.optional()
|
|
8288
8693
|
});
|
|
8289
|
-
var
|
|
8290
|
-
type: external_exports.literal("
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
environment: external_exports.string(),
|
|
8294
|
-
status: V1StatusSchema,
|
|
8295
|
-
summary: V1StatisticsSchema
|
|
8694
|
+
var V1WsRunCoverageSchema = external_exports.object({
|
|
8695
|
+
type: external_exports.literal("run:v1:coverage"),
|
|
8696
|
+
runId: external_exports.string(),
|
|
8697
|
+
coverage: V1CoverageReportSchema
|
|
8296
8698
|
});
|
|
8297
8699
|
external_exports.union([
|
|
8298
8700
|
V1WsRunStartedSchema,
|
|
@@ -8301,19 +8703,360 @@ external_exports.union([
|
|
|
8301
8703
|
V1WsTestExecutionSchema,
|
|
8302
8704
|
V1WsOutlineExampleResultsSchema,
|
|
8303
8705
|
V1WsRunCompletedSchema,
|
|
8304
|
-
|
|
8706
|
+
V1WsRunCoverageSchema
|
|
8305
8707
|
]);
|
|
8708
|
+
var metricNames = ["lines", "branches", "functions", "statements"];
|
|
8709
|
+
function collectCoverageReport(options = {}) {
|
|
8710
|
+
const envEnabled = parseBoolean(process.env.LIVEDOC_COVERAGE);
|
|
8711
|
+
const configuredPath = firstNonEmpty(options.artifactPath, process.env.LIVEDOC_COVERAGE_PATH);
|
|
8712
|
+
const thresholds = resolveThresholds(options.thresholds);
|
|
8713
|
+
const rootDir = path2__default.resolve(options.rootDir || process.cwd());
|
|
8714
|
+
const reportsDirectory = options.reportsDirectory ? resolvePath(rootDir, options.reportsDirectory) : void 0;
|
|
8715
|
+
const explicit = envEnabled === true || options.enabled === true || !!configuredPath;
|
|
8716
|
+
if (options.coverageMap !== void 0) {
|
|
8717
|
+
try {
|
|
8718
|
+
const report = parseIstanbulCoverageMap(options.coverageMap, rootDir);
|
|
8719
|
+
const applied = applyThresholds(report, thresholds);
|
|
8720
|
+
return {
|
|
8721
|
+
...applied,
|
|
8722
|
+
provenance: {
|
|
8723
|
+
tool: "vitest",
|
|
8724
|
+
format: "istanbul-coverage-map",
|
|
8725
|
+
detected: "auto",
|
|
8726
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8727
|
+
}
|
|
8728
|
+
};
|
|
8729
|
+
} catch (error) {
|
|
8730
|
+
return diagnosticReport(
|
|
8731
|
+
"parse-failed",
|
|
8732
|
+
"warning",
|
|
8733
|
+
`LiveDoc could not parse Vitest's in-memory coverage map: ${error instanceof Error ? error.message : String(error)}`
|
|
8734
|
+
);
|
|
8735
|
+
}
|
|
8736
|
+
}
|
|
8737
|
+
const candidates = findCandidates(rootDir, reportsDirectory, configuredPath);
|
|
8738
|
+
if (candidates.length === 0) {
|
|
8739
|
+
return explicit ? diagnosticReport("artifact-missing", "warning", "Coverage was enabled, but LiveDoc could not find a supported coverage artifact.") : void 0;
|
|
8740
|
+
}
|
|
8741
|
+
const stale = [];
|
|
8742
|
+
for (const candidate of candidates) {
|
|
8743
|
+
if (!existsSync(candidate.path)) continue;
|
|
8744
|
+
let fileStats;
|
|
8745
|
+
try {
|
|
8746
|
+
fileStats = statSync(candidate.path);
|
|
8747
|
+
} catch (error) {
|
|
8748
|
+
return diagnosticReport(
|
|
8749
|
+
"parse-failed",
|
|
8750
|
+
"warning",
|
|
8751
|
+
`LiveDoc could not read the coverage artifact metadata: ${error instanceof Error ? error.message : String(error)}`,
|
|
8752
|
+
candidate.path
|
|
8753
|
+
);
|
|
8754
|
+
}
|
|
8755
|
+
const generatedAt = fileStats.mtime.toISOString();
|
|
8756
|
+
if (isStale(fileStats, options.runStartedAt)) {
|
|
8757
|
+
stale.push({
|
|
8758
|
+
severity: "warning",
|
|
8759
|
+
code: "stale",
|
|
8760
|
+
message: "LiveDoc found a coverage artifact, but it appears older than the current test run.",
|
|
8761
|
+
path: candidate.path
|
|
8762
|
+
});
|
|
8763
|
+
if (candidate.detected === "configured") {
|
|
8764
|
+
return { status: "invalid", diagnostics: stale };
|
|
8765
|
+
}
|
|
8766
|
+
continue;
|
|
8767
|
+
}
|
|
8768
|
+
if (candidate.format === "unsupported") {
|
|
8769
|
+
return diagnosticReport(
|
|
8770
|
+
"unsupported-format",
|
|
8771
|
+
"warning",
|
|
8772
|
+
`LiveDoc does not support this coverage artifact format yet: ${path2__default.basename(candidate.path)}.`,
|
|
8773
|
+
candidate.path
|
|
8774
|
+
);
|
|
8775
|
+
}
|
|
8776
|
+
try {
|
|
8777
|
+
const report = candidate.format === "istanbul-json-summary" ? parseIstanbulSummary(candidate.path, rootDir) : parseLcov(candidate.path, rootDir);
|
|
8778
|
+
const applied = applyThresholds(report, thresholds);
|
|
8779
|
+
return {
|
|
8780
|
+
...applied,
|
|
8781
|
+
provenance: {
|
|
8782
|
+
tool: candidate.format === "lcov" ? "lcov" : "istanbul",
|
|
8783
|
+
format: candidate.format,
|
|
8784
|
+
path: candidate.path,
|
|
8785
|
+
detected: candidate.detected,
|
|
8786
|
+
generatedAt
|
|
8787
|
+
}
|
|
8788
|
+
};
|
|
8789
|
+
} catch (error) {
|
|
8790
|
+
return diagnosticReport(
|
|
8791
|
+
"parse-failed",
|
|
8792
|
+
"warning",
|
|
8793
|
+
`LiveDoc could not parse the coverage artifact: ${error instanceof Error ? error.message : String(error)}`,
|
|
8794
|
+
candidate.path
|
|
8795
|
+
);
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8798
|
+
if (stale.length > 0 && explicit) {
|
|
8799
|
+
return { status: "invalid", diagnostics: stale };
|
|
8800
|
+
}
|
|
8801
|
+
return void 0;
|
|
8802
|
+
}
|
|
8803
|
+
function parseIstanbulSummary(filePath, rootDir) {
|
|
8804
|
+
const data = JSON.parse(readFileSync(filePath, "utf8"));
|
|
8805
|
+
if (!isRecord(data)) {
|
|
8806
|
+
throw new Error("Istanbul summary root must be an object.");
|
|
8807
|
+
}
|
|
8808
|
+
const total = data.total;
|
|
8809
|
+
if (!isRecord(total)) {
|
|
8810
|
+
throw new Error("Istanbul summary is missing the total coverage block.");
|
|
8811
|
+
}
|
|
8812
|
+
const files = Object.entries(data).filter(([key]) => key !== "total").map(([file, value]) => ({
|
|
8813
|
+
path: normalizeCoveragePath(file, rootDir),
|
|
8814
|
+
summary: normalizeSummary(value)
|
|
8815
|
+
})).filter((file) => Object.keys(file.summary).length > 0);
|
|
8816
|
+
return {
|
|
8817
|
+
status: "available",
|
|
8818
|
+
summary: normalizeSummary(total),
|
|
8819
|
+
files
|
|
8820
|
+
};
|
|
8821
|
+
}
|
|
8822
|
+
function parseIstanbulCoverageMap(value, rootDir) {
|
|
8823
|
+
if (!isIstanbulCoverageMap(value)) {
|
|
8824
|
+
throw new Error("Coverage value does not expose the Istanbul CoverageMap API.");
|
|
8825
|
+
}
|
|
8826
|
+
const files = value.files().map((file) => {
|
|
8827
|
+
const fileCoverage = value.fileCoverageFor(file);
|
|
8828
|
+
const summary = fileCoverage.toSummary().toJSON();
|
|
8829
|
+
return {
|
|
8830
|
+
path: normalizeCoveragePath(file, rootDir),
|
|
8831
|
+
summary: normalizeSummary(summary)
|
|
8832
|
+
};
|
|
8833
|
+
}).filter((file) => Object.keys(file.summary).length > 0);
|
|
8834
|
+
return {
|
|
8835
|
+
status: "available",
|
|
8836
|
+
summary: normalizeSummary(value.getCoverageSummary().toJSON()),
|
|
8837
|
+
files
|
|
8838
|
+
};
|
|
8839
|
+
}
|
|
8840
|
+
function parseLcov(filePath, rootDir) {
|
|
8841
|
+
const text = readFileSync(filePath, "utf8");
|
|
8842
|
+
const files = [];
|
|
8843
|
+
let current;
|
|
8844
|
+
const flush = () => {
|
|
8845
|
+
if (!current?.path) return;
|
|
8846
|
+
const lineValues = Array.from(current.lines.values());
|
|
8847
|
+
const branchValues = current.branches;
|
|
8848
|
+
const functionValues = Array.from(current.functions.values());
|
|
8849
|
+
const summary = {
|
|
8850
|
+
lines: metricFromHits(lineValues)
|
|
8851
|
+
};
|
|
8852
|
+
if (branchValues.length > 0) summary.branches = metricFromHits(branchValues);
|
|
8853
|
+
if (functionValues.length > 0) summary.functions = metricFromHits(functionValues);
|
|
8854
|
+
files.push({ path: normalizeCoveragePath(current.path, rootDir), summary });
|
|
8855
|
+
};
|
|
8856
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
8857
|
+
const line = rawLine.trim();
|
|
8858
|
+
if (!line) continue;
|
|
8859
|
+
if (line === "end_of_record") {
|
|
8860
|
+
flush();
|
|
8861
|
+
current = void 0;
|
|
8862
|
+
continue;
|
|
8863
|
+
}
|
|
8864
|
+
if (line.startsWith("SF:")) {
|
|
8865
|
+
flush();
|
|
8866
|
+
current = { path: line.slice(3), lines: /* @__PURE__ */ new Map(), branches: [], functions: /* @__PURE__ */ new Map() };
|
|
8867
|
+
continue;
|
|
8868
|
+
}
|
|
8869
|
+
if (!current) continue;
|
|
8870
|
+
if (line.startsWith("DA:")) {
|
|
8871
|
+
const [lineNumber, hits] = line.slice(3).split(",");
|
|
8872
|
+
const parsedLine = Number(lineNumber);
|
|
8873
|
+
const parsedHits = Number(hits);
|
|
8874
|
+
if (Number.isFinite(parsedLine) && Number.isFinite(parsedHits)) current.lines.set(parsedLine, parsedHits);
|
|
8875
|
+
continue;
|
|
8876
|
+
}
|
|
8877
|
+
if (line.startsWith("BRDA:")) {
|
|
8878
|
+
const parts = line.slice(5).split(",");
|
|
8879
|
+
const taken = parts[3] === "-" ? null : Number(parts[3]);
|
|
8880
|
+
current.branches.push(Number.isFinite(taken) ? taken : null);
|
|
8881
|
+
continue;
|
|
8882
|
+
}
|
|
8883
|
+
if (line.startsWith("FNDA:")) {
|
|
8884
|
+
const [hits, name] = line.slice(5).split(",");
|
|
8885
|
+
const parsedHits = Number(hits);
|
|
8886
|
+
if (name && Number.isFinite(parsedHits)) current.functions.set(name, parsedHits);
|
|
8887
|
+
}
|
|
8888
|
+
}
|
|
8889
|
+
flush();
|
|
8890
|
+
return {
|
|
8891
|
+
status: "available",
|
|
8892
|
+
summary: aggregateSummaries(files.map((file) => file.summary)),
|
|
8893
|
+
files
|
|
8894
|
+
};
|
|
8895
|
+
}
|
|
8896
|
+
function metricFromHits(values) {
|
|
8897
|
+
const total = values.length;
|
|
8898
|
+
const covered = values.filter((value) => typeof value === "number" && value > 0).length;
|
|
8899
|
+
return makeMetric(covered, total);
|
|
8900
|
+
}
|
|
8901
|
+
function normalizeSummary(value) {
|
|
8902
|
+
const summary = {};
|
|
8903
|
+
if (!isRecord(value)) return summary;
|
|
8904
|
+
for (const metric of metricNames) {
|
|
8905
|
+
const raw = value[metric];
|
|
8906
|
+
if (!isRecord(raw)) continue;
|
|
8907
|
+
const total = Number(raw.total);
|
|
8908
|
+
const covered = Number(raw.covered);
|
|
8909
|
+
const skipped = Number(raw.skipped);
|
|
8910
|
+
const pct = raw.pct === "Unknown" || raw.pct === void 0 || raw.pct === null ? calculatePct(covered, total) : Number(raw.pct);
|
|
8911
|
+
if (!Number.isFinite(total) || !Number.isFinite(covered)) continue;
|
|
8912
|
+
summary[metric] = {
|
|
8913
|
+
covered,
|
|
8914
|
+
total,
|
|
8915
|
+
skipped: Number.isFinite(skipped) ? skipped : void 0,
|
|
8916
|
+
pct: Number.isFinite(pct) ? clampPct(pct) : calculatePct(covered, total)
|
|
8917
|
+
};
|
|
8918
|
+
}
|
|
8919
|
+
return summary;
|
|
8920
|
+
}
|
|
8921
|
+
function aggregateSummaries(summaries) {
|
|
8922
|
+
const aggregate = {};
|
|
8923
|
+
for (const metric of metricNames) {
|
|
8924
|
+
let covered = 0;
|
|
8925
|
+
let total = 0;
|
|
8926
|
+
let skipped = 0;
|
|
8927
|
+
let hasMetric = false;
|
|
8928
|
+
for (const summary of summaries) {
|
|
8929
|
+
const value = summary[metric];
|
|
8930
|
+
if (!value) continue;
|
|
8931
|
+
covered += value.covered;
|
|
8932
|
+
total += value.total;
|
|
8933
|
+
skipped += value.skipped ?? 0;
|
|
8934
|
+
hasMetric = true;
|
|
8935
|
+
}
|
|
8936
|
+
if (hasMetric) aggregate[metric] = { ...makeMetric(covered, total), skipped };
|
|
8937
|
+
}
|
|
8938
|
+
return aggregate;
|
|
8939
|
+
}
|
|
8940
|
+
function applyThresholds(report, thresholds) {
|
|
8941
|
+
const entries = Object.entries(thresholds);
|
|
8942
|
+
if (entries.length === 0) return report;
|
|
8943
|
+
const diagnostics = [...report.diagnostics ?? []];
|
|
8944
|
+
const applied = entries.map(([metric, minimum]) => {
|
|
8945
|
+
const actual = report.summary?.[metric]?.pct ?? null;
|
|
8946
|
+
const status = actual !== null && actual < minimum ? "warning" : "passed";
|
|
8947
|
+
if (status === "warning") {
|
|
8948
|
+
diagnostics.push({
|
|
8949
|
+
severity: "warning",
|
|
8950
|
+
code: "threshold-warning",
|
|
8951
|
+
message: `${metric} coverage is ${actual?.toFixed(1)}%, below the configured ${minimum}% threshold.`
|
|
8952
|
+
});
|
|
8953
|
+
}
|
|
8954
|
+
return { metric, minimum, actual, status };
|
|
8955
|
+
});
|
|
8956
|
+
return {
|
|
8957
|
+
...report,
|
|
8958
|
+
thresholds: applied,
|
|
8959
|
+
diagnostics: diagnostics.length > 0 ? diagnostics : void 0
|
|
8960
|
+
};
|
|
8961
|
+
}
|
|
8962
|
+
function findCandidates(rootDir, reportsDirectory, configuredPath) {
|
|
8963
|
+
const candidates = [];
|
|
8964
|
+
const add = (candidatePath, detected) => {
|
|
8965
|
+
if (!candidatePath) return;
|
|
8966
|
+
const resolved = resolvePath(rootDir, candidatePath);
|
|
8967
|
+
if (candidates.some((candidate) => candidate.path.toLowerCase() === resolved.toLowerCase())) return;
|
|
8968
|
+
candidates.push({ path: resolved, format: detectFormat(resolved), detected });
|
|
8969
|
+
};
|
|
8970
|
+
add(configuredPath, "configured");
|
|
8971
|
+
add(reportsDirectory ? path2__default.join(reportsDirectory, "coverage-summary.json") : void 0, "auto");
|
|
8972
|
+
add(reportsDirectory ? path2__default.join(reportsDirectory, "lcov.info") : void 0, "auto");
|
|
8973
|
+
add(path2__default.join(rootDir, "coverage", "coverage-summary.json"), "auto");
|
|
8974
|
+
add(path2__default.join(rootDir, "coverage", "lcov.info"), "auto");
|
|
8975
|
+
add(path2__default.join(process.cwd(), "coverage", "coverage-summary.json"), "auto");
|
|
8976
|
+
add(path2__default.join(process.cwd(), "coverage", "lcov.info"), "auto");
|
|
8977
|
+
return candidates.filter((candidate) => existsSync(candidate.path) || candidate.detected === "configured");
|
|
8978
|
+
}
|
|
8979
|
+
function detectFormat(filePath) {
|
|
8980
|
+
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
8981
|
+
if (normalized.endsWith("/coverage-summary.json") || normalized.endsWith("\\coverage-summary.json")) return "istanbul-json-summary";
|
|
8982
|
+
if (normalized.endsWith(".info") || normalized.endsWith(".lcov")) return "lcov";
|
|
8983
|
+
return "unsupported";
|
|
8984
|
+
}
|
|
8985
|
+
function diagnosticReport(code, severity, message, filePath) {
|
|
8986
|
+
return {
|
|
8987
|
+
status: severity === "info" ? "not-collected" : "invalid",
|
|
8988
|
+
diagnostics: [{ severity, code, message, path: filePath }]
|
|
8989
|
+
};
|
|
8990
|
+
}
|
|
8991
|
+
function normalizeCoveragePath(filePath, rootDir) {
|
|
8992
|
+
const absolute = path2__default.isAbsolute(filePath) ? path2__default.normalize(filePath) : path2__default.resolve(rootDir, filePath);
|
|
8993
|
+
let relative = path2__default.relative(rootDir, absolute);
|
|
8994
|
+
if (!relative || relative.startsWith("..")) relative = filePath;
|
|
8995
|
+
return relative.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
8996
|
+
}
|
|
8997
|
+
function makeMetric(covered, total) {
|
|
8998
|
+
return { covered, total, pct: calculatePct(covered, total) };
|
|
8999
|
+
}
|
|
9000
|
+
function calculatePct(covered, total) {
|
|
9001
|
+
if (!Number.isFinite(total) || total <= 0) return null;
|
|
9002
|
+
return clampPct(covered / total * 100);
|
|
9003
|
+
}
|
|
9004
|
+
function clampPct(value) {
|
|
9005
|
+
return Math.max(0, Math.min(100, Math.round(value * 10) / 10));
|
|
9006
|
+
}
|
|
9007
|
+
function isStale(fileStats, runStartedAt) {
|
|
9008
|
+
if (!runStartedAt || !Number.isFinite(runStartedAt)) return false;
|
|
9009
|
+
return fileStats.mtimeMs + 2e3 < runStartedAt;
|
|
9010
|
+
}
|
|
9011
|
+
function resolveThresholds(configured) {
|
|
9012
|
+
const thresholds = {};
|
|
9013
|
+
for (const metric of metricNames) {
|
|
9014
|
+
const fromOptions = configured?.[metric];
|
|
9015
|
+
const fromEnv = process.env[`LIVEDOC_COVERAGE_THRESHOLD_${metric.toUpperCase()}`];
|
|
9016
|
+
const parsed = Number(fromOptions ?? fromEnv);
|
|
9017
|
+
if (Number.isFinite(parsed) && parsed >= 0 && parsed <= 100) thresholds[metric] = parsed;
|
|
9018
|
+
}
|
|
9019
|
+
return thresholds;
|
|
9020
|
+
}
|
|
9021
|
+
function parseBoolean(value) {
|
|
9022
|
+
if (value === void 0) return void 0;
|
|
9023
|
+
const normalized = value.trim().toLowerCase();
|
|
9024
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
9025
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
9026
|
+
return void 0;
|
|
9027
|
+
}
|
|
9028
|
+
function firstNonEmpty(...values) {
|
|
9029
|
+
for (const value of values) {
|
|
9030
|
+
const trimmed = value?.trim();
|
|
9031
|
+
if (trimmed) return trimmed;
|
|
9032
|
+
}
|
|
9033
|
+
return void 0;
|
|
9034
|
+
}
|
|
9035
|
+
function resolvePath(rootDir, value) {
|
|
9036
|
+
return path2__default.isAbsolute(value) ? path2__default.normalize(value) : path2__default.resolve(rootDir, value);
|
|
9037
|
+
}
|
|
9038
|
+
function isRecord(value) {
|
|
9039
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9040
|
+
}
|
|
9041
|
+
function isIstanbulCoverageMap(value) {
|
|
9042
|
+
if (!isRecord(value)) return false;
|
|
9043
|
+
return typeof value.getCoverageSummary === "function" && typeof value.files === "function" && typeof value.fileCoverageFor === "function";
|
|
9044
|
+
}
|
|
8306
9045
|
|
|
8307
9046
|
// _src/app/reporter/LiveDocViewerReporterV1.ts
|
|
8308
9047
|
var LiveDocViewerReporter = class {
|
|
8309
9048
|
options;
|
|
9049
|
+
hasExplicitRunType;
|
|
8310
9050
|
constructor(options) {
|
|
9051
|
+
this.hasExplicitRunType = options?.runType !== void 0;
|
|
8311
9052
|
this.options = {
|
|
8312
9053
|
server: options?.server || "http://localhost:3100",
|
|
8313
|
-
project: options?.project || "
|
|
9054
|
+
project: options?.project || "livedoc",
|
|
8314
9055
|
environment: options?.environment || "local",
|
|
9056
|
+
runType: this.parseRunType(options?.runType ?? process.env.LIVEDOC_RUN_TYPE),
|
|
8315
9057
|
timeout: options?.timeout || 1e4,
|
|
8316
|
-
silent: options?.silent ?? true
|
|
9058
|
+
silent: options?.silent ?? true,
|
|
9059
|
+
coverage: options?.coverage || {}
|
|
8317
9060
|
};
|
|
8318
9061
|
}
|
|
8319
9062
|
async execute(results, rawOptions) {
|
|
@@ -8339,7 +9082,7 @@ var LiveDocViewerReporter = class {
|
|
|
8339
9082
|
const testCase = this.buildSuiteTestCase(suite, pathContext);
|
|
8340
9083
|
await this.upsertTestCase(runId, testCase);
|
|
8341
9084
|
}
|
|
8342
|
-
await this.completeRun(runId, results);
|
|
9085
|
+
await this.completeRun(runId, results, rawOptions);
|
|
8343
9086
|
} catch (error) {
|
|
8344
9087
|
if (!this.options.silent) {
|
|
8345
9088
|
console.error("LiveDocViewerReporter error:", error);
|
|
@@ -8352,6 +9095,11 @@ var LiveDocViewerReporter = class {
|
|
|
8352
9095
|
*/
|
|
8353
9096
|
buildTestRun(results, rawOptions) {
|
|
8354
9097
|
this.applyRawOptions(rawOptions);
|
|
9098
|
+
if (this.options.runType === "partial") {
|
|
9099
|
+
throw new Error(
|
|
9100
|
+
"partial-export-unsupported: Partial runs require server history and cannot be exported directly."
|
|
9101
|
+
);
|
|
9102
|
+
}
|
|
8355
9103
|
const pathContext = this.buildPathContext(results);
|
|
8356
9104
|
const documents = [];
|
|
8357
9105
|
for (const feature3 of results.features) {
|
|
@@ -8365,6 +9113,7 @@ var LiveDocViewerReporter = class {
|
|
|
8365
9113
|
}
|
|
8366
9114
|
const { summary, duration } = this.calculateSummary(results);
|
|
8367
9115
|
const status = this.calculateOverallStatus(results);
|
|
9116
|
+
const coverage = this.collectCoverage(rawOptions);
|
|
8368
9117
|
return {
|
|
8369
9118
|
protocolVersion: "1.0",
|
|
8370
9119
|
runId: randomUUID(),
|
|
@@ -8375,7 +9124,8 @@ var LiveDocViewerReporter = class {
|
|
|
8375
9124
|
duration,
|
|
8376
9125
|
status,
|
|
8377
9126
|
summary,
|
|
8378
|
-
documents
|
|
9127
|
+
documents,
|
|
9128
|
+
...coverage ? { coverage } : {}
|
|
8379
9129
|
};
|
|
8380
9130
|
}
|
|
8381
9131
|
async startRunSession(rawOptions) {
|
|
@@ -8414,22 +9164,44 @@ var LiveDocViewerReporter = class {
|
|
|
8414
9164
|
if (rawOptions?.["viewer-environment"]) {
|
|
8415
9165
|
this.options.environment = rawOptions["viewer-environment"];
|
|
8416
9166
|
}
|
|
9167
|
+
if (!this.hasExplicitRunType && rawOptions?.["viewer-run-type"]) {
|
|
9168
|
+
this.options.runType = this.parseRunType(rawOptions["viewer-run-type"]);
|
|
9169
|
+
}
|
|
8417
9170
|
if (rawOptions?.["viewer-timeout"] !== void 0) {
|
|
8418
9171
|
const parsed = Number(rawOptions["viewer-timeout"]);
|
|
8419
9172
|
if (Number.isFinite(parsed) && parsed > 0) {
|
|
8420
9173
|
this.options.timeout = parsed;
|
|
8421
9174
|
}
|
|
8422
9175
|
}
|
|
9176
|
+
const coverage = rawOptions?.coverage;
|
|
9177
|
+
if (coverage && typeof coverage === "object") {
|
|
9178
|
+
this.options.coverage = {
|
|
9179
|
+
...this.options.coverage,
|
|
9180
|
+
enabled: coverage.enabled ?? this.options.coverage.enabled,
|
|
9181
|
+
path: coverage.path ?? coverage.artifactPath ?? this.options.coverage.path,
|
|
9182
|
+
thresholds: coverage.thresholds ?? this.options.coverage.thresholds
|
|
9183
|
+
};
|
|
9184
|
+
}
|
|
9185
|
+
if (rawOptions?.["coverage-path"]) {
|
|
9186
|
+
this.options.coverage.path = String(rawOptions["coverage-path"]);
|
|
9187
|
+
}
|
|
8423
9188
|
}
|
|
8424
9189
|
async startRun() {
|
|
8425
9190
|
const request = {
|
|
8426
9191
|
project: this.options.project,
|
|
8427
9192
|
environment: this.options.environment,
|
|
8428
|
-
framework: "vitest"
|
|
9193
|
+
framework: "vitest",
|
|
9194
|
+
runType: this.options.runType
|
|
8429
9195
|
};
|
|
8430
9196
|
const response = await this.post("/api/v1/runs/start", request);
|
|
8431
9197
|
return response?.runId || null;
|
|
8432
9198
|
}
|
|
9199
|
+
parseRunType(value) {
|
|
9200
|
+
if (value === void 0 || value === null || value === "") return "full";
|
|
9201
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : value;
|
|
9202
|
+
if (normalized === "full" || normalized === "partial") return normalized;
|
|
9203
|
+
throw new Error(`Invalid LiveDoc run type '${String(value)}'. Expected 'full' or 'partial'.`);
|
|
9204
|
+
}
|
|
8433
9205
|
async upsertTestCase(runId, testCase) {
|
|
8434
9206
|
await this.post(`/api/v1/runs/${runId}/testcases`, { testCase });
|
|
8435
9207
|
}
|
|
@@ -8444,7 +9216,7 @@ var LiveDocViewerReporter = class {
|
|
|
8444
9216
|
for (const s of results.suites || []) {
|
|
8445
9217
|
if (s?.filename) filenames.push(s.filename);
|
|
8446
9218
|
}
|
|
8447
|
-
const abs = filenames.filter(Boolean).map((p) =>
|
|
9219
|
+
const abs = filenames.filter(Boolean).map((p) => path2.isAbsolute(p) ? p : path2.resolve(process.cwd(), p)).map((p) => p.replace(/\\/g, "/"));
|
|
8448
9220
|
const rootPath = this.findCommonRootPath(abs);
|
|
8449
9221
|
return { rootPath };
|
|
8450
9222
|
}
|
|
@@ -8466,7 +9238,7 @@ var LiveDocViewerReporter = class {
|
|
|
8466
9238
|
}
|
|
8467
9239
|
buildFileInfo(filename, rootPath) {
|
|
8468
9240
|
const raw = filename || "";
|
|
8469
|
-
const abs = raw ?
|
|
9241
|
+
const abs = raw ? path2.isAbsolute(raw) ? raw : path2.resolve(process.cwd(), raw) : "";
|
|
8470
9242
|
const normalized = abs.replace(/\\/g, "/");
|
|
8471
9243
|
const root = (rootPath || "").replace(/\\/g, "/").replace(/\/+$/g, "");
|
|
8472
9244
|
if (!normalized) {
|
|
@@ -9020,16 +9792,31 @@ Actual: ${step.exception.actual}` : void 0)
|
|
|
9020
9792
|
if (value instanceof Date) return { value: value.toISOString(), type: "date" };
|
|
9021
9793
|
return { value, type: "object" };
|
|
9022
9794
|
}
|
|
9023
|
-
async completeRun(runId, results) {
|
|
9795
|
+
async completeRun(runId, results, rawOptions) {
|
|
9024
9796
|
const { summary, duration } = this.calculateSummary(results);
|
|
9025
9797
|
const overallStatus = this.calculateOverallStatus(results);
|
|
9798
|
+
const coverage = this.collectCoverage(rawOptions);
|
|
9026
9799
|
const request = {
|
|
9027
9800
|
status: overallStatus,
|
|
9028
9801
|
duration,
|
|
9029
|
-
summary
|
|
9802
|
+
summary,
|
|
9803
|
+
...coverage ? { coverage } : {}
|
|
9030
9804
|
};
|
|
9031
9805
|
await this.post(`/api/v1/runs/${runId}/complete`, request);
|
|
9032
9806
|
}
|
|
9807
|
+
collectCoverage(rawOptions) {
|
|
9808
|
+
const context = rawOptions?.coverageContext ?? {};
|
|
9809
|
+
const options = {
|
|
9810
|
+
enabled: this.options.coverage.enabled ?? context.enabled,
|
|
9811
|
+
coverageMap: context.coverageMap,
|
|
9812
|
+
artifactPath: this.options.coverage.path ?? context.artifactPath,
|
|
9813
|
+
rootDir: context.rootDir,
|
|
9814
|
+
reportsDirectory: context.reportsDirectory,
|
|
9815
|
+
runStartedAt: context.runStartedAt,
|
|
9816
|
+
thresholds: this.options.coverage.thresholds ?? context.thresholds
|
|
9817
|
+
};
|
|
9818
|
+
return collectCoverageReport(options);
|
|
9819
|
+
}
|
|
9033
9820
|
mapStatus(status) {
|
|
9034
9821
|
switch (status) {
|
|
9035
9822
|
case "pass" /* pass */:
|
|
@@ -9178,6 +9965,7 @@ var LiveDocSpecReporter = class {
|
|
|
9178
9965
|
liveDocSpec;
|
|
9179
9966
|
options;
|
|
9180
9967
|
exportConfig = null;
|
|
9968
|
+
coverageContext = { runStartedAt: Date.now() };
|
|
9181
9969
|
streamEnabled = true;
|
|
9182
9970
|
taskById = /* @__PURE__ */ new Map();
|
|
9183
9971
|
streamedStates = /* @__PURE__ */ new Map();
|
|
@@ -9204,6 +9992,7 @@ var LiveDocSpecReporter = class {
|
|
|
9204
9992
|
livedoc$1.options.publish.server = options.publish.server ?? livedoc$1.options.publish.server;
|
|
9205
9993
|
livedoc$1.options.publish.project = options.publish.project ?? livedoc$1.options.publish.project;
|
|
9206
9994
|
livedoc$1.options.publish.environment = options.publish.environment ?? livedoc$1.options.publish.environment;
|
|
9995
|
+
livedoc$1.options.publish.runType = options.publish.runType ?? livedoc$1.options.publish.runType;
|
|
9207
9996
|
}
|
|
9208
9997
|
if (options.export) {
|
|
9209
9998
|
this.exportConfig = {
|
|
@@ -9220,22 +10009,14 @@ var LiveDocSpecReporter = class {
|
|
|
9220
10009
|
this.setLiveDocOptions(this.options);
|
|
9221
10010
|
}
|
|
9222
10011
|
async onInit(ctx) {
|
|
10012
|
+
this.captureCoverageContext(ctx);
|
|
9223
10013
|
this.liveDocSpec.executionStart();
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
10014
|
+
const envServerUrl = this.applyPublishEnvironmentOverrides();
|
|
10015
|
+
if (!envServerUrl && !livedoc$1.options.publish.enabled) {
|
|
10016
|
+
const serverInfo = await this.discoverLiveDocServer();
|
|
10017
|
+
if (serverInfo) {
|
|
9227
10018
|
livedoc$1.options.publish.enabled = true;
|
|
9228
|
-
livedoc$1.options.publish.server =
|
|
9229
|
-
} else {
|
|
9230
|
-
try {
|
|
9231
|
-
const { discoverServer } = await import('@swedevtools/livedoc-server');
|
|
9232
|
-
const serverInfo = await discoverServer();
|
|
9233
|
-
if (serverInfo) {
|
|
9234
|
-
livedoc$1.options.publish.enabled = true;
|
|
9235
|
-
livedoc$1.options.publish.server = serverInfo.url;
|
|
9236
|
-
}
|
|
9237
|
-
} catch {
|
|
9238
|
-
}
|
|
10019
|
+
livedoc$1.options.publish.server = serverInfo.url;
|
|
9239
10020
|
}
|
|
9240
10021
|
}
|
|
9241
10022
|
if (livedoc$1.options.publish.enabled) {
|
|
@@ -9247,6 +10028,102 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9247
10028
|
`);
|
|
9248
10029
|
}
|
|
9249
10030
|
}
|
|
10031
|
+
onCoverage(coverageMap) {
|
|
10032
|
+
this.coverageContext.coverageMap = coverageMap;
|
|
10033
|
+
}
|
|
10034
|
+
captureCoverageContext(ctx) {
|
|
10035
|
+
const config = ctx?.config ?? {};
|
|
10036
|
+
const coverage = config.coverage ?? {};
|
|
10037
|
+
const rawOptions = this.options.rawOptions ?? {};
|
|
10038
|
+
const reporterCoverage = rawOptions.coverage ?? {};
|
|
10039
|
+
const rootDir = config.root || process.cwd();
|
|
10040
|
+
const reportsDirectory = coverage.reportsDirectory ? resolve(rootDir, coverage.reportsDirectory) : void 0;
|
|
10041
|
+
this.coverageContext = {
|
|
10042
|
+
enabled: Boolean(coverage.enabled) || Boolean(reporterCoverage.enabled),
|
|
10043
|
+
artifactPath: reporterCoverage.path ?? reporterCoverage.artifactPath,
|
|
10044
|
+
rootDir,
|
|
10045
|
+
reportsDirectory,
|
|
10046
|
+
runStartedAt: Date.now(),
|
|
10047
|
+
thresholds: reporterCoverage.thresholds
|
|
10048
|
+
};
|
|
10049
|
+
}
|
|
10050
|
+
applyPublishEnvironmentOverrides() {
|
|
10051
|
+
const envServerUrl = this.firstEnvironmentValue(
|
|
10052
|
+
"LIVEDOC_SERVER_URL",
|
|
10053
|
+
"LIVEDOC_PUBLISH_SERVER",
|
|
10054
|
+
"LIVEDOC_VIEWER_SERVER"
|
|
10055
|
+
);
|
|
10056
|
+
const envProject = this.firstEnvironmentValue(
|
|
10057
|
+
"LIVEDOC_PROJECT",
|
|
10058
|
+
"LIVEDOC_PUBLISH_PROJECT",
|
|
10059
|
+
"LIVEDOC_VIEWER_PROJECT"
|
|
10060
|
+
);
|
|
10061
|
+
const envEnvironment = this.firstEnvironmentValue(
|
|
10062
|
+
"LIVEDOC_ENVIRONMENT",
|
|
10063
|
+
"LIVEDOC_PUBLISH_ENV",
|
|
10064
|
+
"LIVEDOC_VIEWER_ENV"
|
|
10065
|
+
);
|
|
10066
|
+
const envRunType = this.firstEnvironmentValue("LIVEDOC_RUN_TYPE");
|
|
10067
|
+
if (envProject) {
|
|
10068
|
+
livedoc$1.options.publish.project = envProject;
|
|
10069
|
+
}
|
|
10070
|
+
if (envEnvironment) {
|
|
10071
|
+
livedoc$1.options.publish.environment = envEnvironment;
|
|
10072
|
+
}
|
|
10073
|
+
if (envRunType) {
|
|
10074
|
+
const normalizedRunType = envRunType.toLowerCase();
|
|
10075
|
+
if (normalizedRunType !== "full" && normalizedRunType !== "partial") {
|
|
10076
|
+
throw new Error(
|
|
10077
|
+
`Invalid LIVEDOC_RUN_TYPE value '${envRunType}'. Expected 'full' or 'partial'.`
|
|
10078
|
+
);
|
|
10079
|
+
}
|
|
10080
|
+
livedoc$1.options.publish.runType = normalizedRunType;
|
|
10081
|
+
}
|
|
10082
|
+
if (envServerUrl) {
|
|
10083
|
+
livedoc$1.options.publish.enabled = true;
|
|
10084
|
+
livedoc$1.options.publish.server = envServerUrl;
|
|
10085
|
+
}
|
|
10086
|
+
return envServerUrl;
|
|
10087
|
+
}
|
|
10088
|
+
firstEnvironmentValue(...names) {
|
|
10089
|
+
for (const name of names) {
|
|
10090
|
+
const value = process.env[name]?.trim();
|
|
10091
|
+
if (value) return value;
|
|
10092
|
+
}
|
|
10093
|
+
return void 0;
|
|
10094
|
+
}
|
|
10095
|
+
async discoverLiveDocServer() {
|
|
10096
|
+
try {
|
|
10097
|
+
const { discoverServer } = await import('@swedevtools/livedoc-server');
|
|
10098
|
+
const serverInfo = await discoverServer();
|
|
10099
|
+
if (serverInfo) return serverInfo;
|
|
10100
|
+
} catch {
|
|
10101
|
+
}
|
|
10102
|
+
return await this.discoverDefaultLocalServer();
|
|
10103
|
+
}
|
|
10104
|
+
async discoverDefaultLocalServer() {
|
|
10105
|
+
const defaultPort = 3100;
|
|
10106
|
+
const defaultUrl = `http://localhost:${defaultPort}`;
|
|
10107
|
+
if (await this.isLiveDocServerHealthy(defaultUrl)) {
|
|
10108
|
+
return { url: defaultUrl, port: defaultPort };
|
|
10109
|
+
}
|
|
10110
|
+
return null;
|
|
10111
|
+
}
|
|
10112
|
+
async isLiveDocServerHealthy(serverUrl) {
|
|
10113
|
+
const controller = new AbortController();
|
|
10114
|
+
const timeout = setTimeout(() => controller.abort(), 500);
|
|
10115
|
+
try {
|
|
10116
|
+
const response = await fetch(`${serverUrl}/api/health`, {
|
|
10117
|
+
headers: { "Connection": "close" },
|
|
10118
|
+
signal: controller.signal
|
|
10119
|
+
});
|
|
10120
|
+
return response.ok;
|
|
10121
|
+
} catch {
|
|
10122
|
+
return false;
|
|
10123
|
+
} finally {
|
|
10124
|
+
clearTimeout(timeout);
|
|
10125
|
+
}
|
|
10126
|
+
}
|
|
9250
10127
|
onCollected(files) {
|
|
9251
10128
|
if (!this.streamEnabled) return;
|
|
9252
10129
|
this.taskById.clear();
|
|
@@ -9310,30 +10187,34 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9310
10187
|
server: publishOptions.server,
|
|
9311
10188
|
project: publishOptions.project,
|
|
9312
10189
|
environment: publishOptions.environment,
|
|
10190
|
+
runType: publishOptions.runType,
|
|
9313
10191
|
silent: false
|
|
9314
10192
|
});
|
|
9315
|
-
const
|
|
9316
|
-
if (!
|
|
9317
|
-
|
|
10193
|
+
const rawOptions2 = this.options.rawOptions || {};
|
|
10194
|
+
if (!rawOptions2.postReporters) {
|
|
10195
|
+
rawOptions2.postReporters = [];
|
|
9318
10196
|
}
|
|
9319
|
-
|
|
10197
|
+
rawOptions2.postReporters.push(viewerReporter);
|
|
9320
10198
|
}
|
|
9321
|
-
|
|
10199
|
+
const rawOptions = this.options.rawOptions || {};
|
|
10200
|
+
rawOptions.coverageContext = this.coverageContext;
|
|
10201
|
+
await this.liveDocSpec.executionEnd(results, rawOptions);
|
|
9322
10202
|
if (this.exportConfig) {
|
|
9323
|
-
this.exportTestRunJson(results);
|
|
10203
|
+
this.exportTestRunJson(results, rawOptions);
|
|
9324
10204
|
}
|
|
9325
10205
|
}
|
|
9326
|
-
exportTestRunJson(results) {
|
|
10206
|
+
exportTestRunJson(results, rawOptions) {
|
|
9327
10207
|
const exportConfig = this.exportConfig;
|
|
9328
10208
|
const outputPath = resolve(exportConfig.output);
|
|
9329
|
-
const project = exportConfig.project || livedoc$1.options.publish.project || "
|
|
10209
|
+
const project = exportConfig.project || livedoc$1.options.publish.project || "livedoc";
|
|
9330
10210
|
const environment = exportConfig.environment || livedoc$1.options.publish.environment || (process.env.CI ? "ci" : "local");
|
|
9331
10211
|
const converter = new LiveDocViewerReporter({
|
|
9332
10212
|
project,
|
|
9333
10213
|
environment,
|
|
10214
|
+
runType: livedoc$1.options.publish.runType,
|
|
9334
10215
|
silent: true
|
|
9335
10216
|
});
|
|
9336
|
-
const testRun = converter.buildTestRun(results);
|
|
10217
|
+
const testRun = converter.buildTestRun(results, rawOptions);
|
|
9337
10218
|
try {
|
|
9338
10219
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
9339
10220
|
const json = JSON.stringify(testRun, null, 2);
|
|
@@ -9356,9 +10237,15 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9356
10237
|
for (const suite of file.tasks || []) {
|
|
9357
10238
|
if (suite.type === "suite") {
|
|
9358
10239
|
if (suite.name.startsWith("Specification:")) {
|
|
9359
|
-
|
|
10240
|
+
const specification3 = this.buildSpecificationFromSuite(suite, file.filepath);
|
|
10241
|
+
if (specification3.rules.length > 0) {
|
|
10242
|
+
specifications.push(specification3);
|
|
10243
|
+
}
|
|
9360
10244
|
} else if (suite.name.startsWith("Feature:")) {
|
|
9361
|
-
|
|
10245
|
+
const feature3 = this.buildFeatureFromSuite(suite, file.filepath);
|
|
10246
|
+
if (feature3.scenarios.length > 0) {
|
|
10247
|
+
features.push(feature3);
|
|
10248
|
+
}
|
|
9362
10249
|
} else {
|
|
9363
10250
|
suites.push(this.buildVitestSuiteFromTask(suite, file.filepath));
|
|
9364
10251
|
}
|
|
@@ -9403,6 +10290,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9403
10290
|
}
|
|
9404
10291
|
for (const task of tasks) {
|
|
9405
10292
|
if (task === backgroundSuite) continue;
|
|
10293
|
+
if (!this.hasUnfilteredLiveDocLeaf(task)) continue;
|
|
9406
10294
|
if (typeof task.name === "string" && task.name.startsWith("Scenario:")) {
|
|
9407
10295
|
const exampleSuites = (task.tasks || []).filter(
|
|
9408
10296
|
(t) => t.type === "suite" && t.name.startsWith("Example ")
|
|
@@ -9426,7 +10314,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9426
10314
|
scenarioOutline3.description = parsed.description;
|
|
9427
10315
|
scenarioOutline3.tags = parsed.tags;
|
|
9428
10316
|
const exampleSuites = (suite.tasks || []).filter(
|
|
9429
|
-
(t) => t.type === "suite" && t.name.startsWith("Example ")
|
|
10317
|
+
(t) => t.type === "suite" && t.name.startsWith("Example ") && this.hasUnfilteredLiveDocLeaf(t)
|
|
9430
10318
|
);
|
|
9431
10319
|
const firstExampleSuite = exampleSuites[0];
|
|
9432
10320
|
const firstExampleLiveDoc = this.findFirstLiveDocStepMetaInSuite(firstExampleSuite);
|
|
@@ -9478,7 +10366,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9478
10366
|
background3.tags = parsed.tags;
|
|
9479
10367
|
const forcePending = suite?.mode === "skip" || suite?.mode === "todo";
|
|
9480
10368
|
for (const task of suite.tasks || []) {
|
|
9481
|
-
if (task.type === "test") {
|
|
10369
|
+
if (task.type === "test" && !this.isFilteredOutTask(task)) {
|
|
9482
10370
|
const step = this.buildStepFromTest(task, background3, forcePending);
|
|
9483
10371
|
background3.addStep(step);
|
|
9484
10372
|
}
|
|
@@ -9493,7 +10381,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9493
10381
|
scenario3.tags = parsed.tags;
|
|
9494
10382
|
const forcePending = suite?.mode === "skip" || suite?.mode === "todo";
|
|
9495
10383
|
for (const task of suite.tasks || []) {
|
|
9496
|
-
if (task.type === "test") {
|
|
10384
|
+
if (task.type === "test" && !this.isFilteredOutTask(task)) {
|
|
9497
10385
|
const step = this.buildStepFromTest(task, scenario3, forcePending);
|
|
9498
10386
|
scenario3.addStep(step);
|
|
9499
10387
|
}
|
|
@@ -9517,7 +10405,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9517
10405
|
example.example = this.sanitizeExampleKeys(metaExampleValues);
|
|
9518
10406
|
example.exampleRaw = example.example;
|
|
9519
10407
|
for (const task of suite.tasks || []) {
|
|
9520
|
-
if (task.type === "test") {
|
|
10408
|
+
if (task.type === "test" && !this.isFilteredOutTask(task)) {
|
|
9521
10409
|
const step = this.buildStepFromTest(task, example, forcePending);
|
|
9522
10410
|
example.addStep(step);
|
|
9523
10411
|
}
|
|
@@ -9531,9 +10419,20 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9531
10419
|
if (!livedoc3 || typeof livedoc3 !== "object") return void 0;
|
|
9532
10420
|
return livedoc3;
|
|
9533
10421
|
}
|
|
10422
|
+
isFilteredOutTask(task) {
|
|
10423
|
+
return this.getLiveDocMetaFromTask(task)?.filter?.filteredOut === true;
|
|
10424
|
+
}
|
|
10425
|
+
hasUnfilteredLiveDocLeaf(task) {
|
|
10426
|
+
if (task?.type === "test") {
|
|
10427
|
+
const kind = this.getLiveDocMetaFromTask(task)?.kind;
|
|
10428
|
+
return (kind === "step" || kind === "rule" || kind === "ruleExample") && !this.isFilteredOutTask(task);
|
|
10429
|
+
}
|
|
10430
|
+
return Array.isArray(task?.tasks) && task.tasks.some((child) => this.hasUnfilteredLiveDocLeaf(child));
|
|
10431
|
+
}
|
|
9534
10432
|
findFirstLiveDocStepMetaInSuite(suite) {
|
|
9535
10433
|
const tasks = suite?.tasks || [];
|
|
9536
10434
|
for (const task of tasks) {
|
|
10435
|
+
if (this.isFilteredOutTask(task)) continue;
|
|
9537
10436
|
if (task?.type !== "test") continue;
|
|
9538
10437
|
const livedoc3 = this.getLiveDocMetaFromTask(task);
|
|
9539
10438
|
if (livedoc3?.kind === "step") return livedoc3;
|
|
@@ -9721,8 +10620,10 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9721
10620
|
specification3.tags = parsed.tags;
|
|
9722
10621
|
specification3.filename = filepath;
|
|
9723
10622
|
for (const task of suite.tasks || []) {
|
|
10623
|
+
if (!this.hasUnfilteredLiveDocLeaf(task)) continue;
|
|
9724
10624
|
if (task.type === "suite") {
|
|
9725
10625
|
const exampleTests = (task.tasks || []).filter((t) => {
|
|
10626
|
+
if (this.isFilteredOutTask(t)) return false;
|
|
9726
10627
|
if (t.type !== "test") return false;
|
|
9727
10628
|
const meta = this.getLiveDocMetaFromTask(t);
|
|
9728
10629
|
return meta?.kind === "ruleExample";
|
|
@@ -9731,6 +10632,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9731
10632
|
const ruleOutline3 = this.buildRuleOutlineFromSuite(task, specification3);
|
|
9732
10633
|
specification3.rules.push(ruleOutline3);
|
|
9733
10634
|
}
|
|
10635
|
+
if (this.isFilteredOutTask(task)) continue;
|
|
9734
10636
|
} else if (task.type === "test") {
|
|
9735
10637
|
if (task.name.startsWith("Rule:")) {
|
|
9736
10638
|
const rule3 = this.buildRuleFromTest(task, specification3);
|
|
@@ -9779,6 +10681,7 @@ LiveDoc Viewer: Connecting to ${publishOptions.server}...`);
|
|
|
9779
10681
|
buildRuleOutlineFromSuite(suite, specification3) {
|
|
9780
10682
|
const ruleOutline3 = new RuleOutline(specification3);
|
|
9781
10683
|
const exampleTests = (suite.tasks || []).filter((t) => {
|
|
10684
|
+
if (this.isFilteredOutTask(t)) return false;
|
|
9782
10685
|
if (t.type !== "test") return false;
|
|
9783
10686
|
const meta = this.getLiveDocMetaFromTask(t);
|
|
9784
10687
|
return meta?.kind === "ruleExample";
|