doc-detective 3.6.2 → 3.7.0-preview.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/package.json +14 -5
- package/samples/http.spec.yaml +38 -36
- package/samples/kitten-search.spec.json +24 -14
- package/scripts/bump-sync-version-core.js +1 -1
- package/src/cli/App.mjs +39 -0
- package/src/cli/ResultsSummary.mjs +162 -0
- package/src/cli/TestRunner.mjs +41 -0
- package/src/cli/builder/DebugRunner.mjs +963 -0
- package/src/cli/builder/FieldEditor.mjs +433 -0
- package/src/cli/builder/SpecSelector.mjs +250 -0
- package/src/cli/builder/StepEditor.mjs +644 -0
- package/src/cli/builder/TestBuilder.mjs +1159 -0
- package/src/cli/builder/TestEditor.mjs +486 -0
- package/src/cli/builder/builderRunner.js +86 -0
- package/src/cli/builder/components.mjs +442 -0
- package/src/cli/builder/index.js +11 -0
- package/src/cli/builder/schemaUtils.mjs +559 -0
- package/src/cli/builder/sourceFileUtils.js +892 -0
- package/src/cli/runner.js +105 -0
- package/src/index.js +319 -4
- package/src/utils.js +19 -18
- package/test/builder/DebugRunner.test.mjs +441 -0
- package/test/builder/SpecSelector.test.mjs +496 -0
- package/test/builder/StepEditor.test.mjs +379 -0
- package/test/builder/TestBuilder.test.mjs +528 -0
- package/test/builder/TestEditor.test.mjs +672 -0
- package/test/builder/editor.test.mjs +452 -0
- package/test/builder/fixtures.mjs +266 -0
- package/test/builder/inlineSourceUpdates.test.mjs +977 -0
- package/test/builder/schemaUtils.test.mjs +576 -0
- package/test/builder/sourceFileUtils.test.mjs +823 -0
- package/test/resolvedTests.test.js +50 -42
- package/test/server/index.js +26 -24
- package/samples/tests.spec.json +0 -70
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doc-detective",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0-preview.0",
|
|
4
4
|
"description": "Treat doc content as testable assertions to validate doc accuracy and product UX.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"doc-detective": "src/index.js"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"main": "./src/index.js",
|
|
9
9
|
"scripts": {
|
|
10
10
|
"mocha": "mocha",
|
|
11
|
-
"test": "mocha test/*.test.js",
|
|
11
|
+
"test": "mocha --exit test/*.test.js test/builder/*.test.mjs",
|
|
12
12
|
"prerunTests": "node ./src/checkDependencies.js",
|
|
13
13
|
"runTests": "node ./src/index.js runTests",
|
|
14
14
|
"start": "node ./src/index.js",
|
|
@@ -34,14 +34,23 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
36
36
|
"axios": "^1.13.2",
|
|
37
|
-
"doc-detective-common": "^3.
|
|
38
|
-
"doc-detective-core": "^3.
|
|
37
|
+
"doc-detective-common": "^3.7.0-preview.0",
|
|
38
|
+
"doc-detective-core": "^3.7.0-preview.0",
|
|
39
|
+
"doc-detective-resolver": "^3.7.0-preview.0",
|
|
40
|
+
"ink": "^6.5.1",
|
|
41
|
+
"ink-select-input": "^6.2.0",
|
|
42
|
+
"ink-spinner": "^5.0.0",
|
|
43
|
+
"js-yaml": "^4.1.1",
|
|
44
|
+
"react": "^19.2.1",
|
|
45
|
+
"yaml": "^2.8.2",
|
|
39
46
|
"yargs": "^17.7.2"
|
|
40
47
|
},
|
|
41
48
|
"devDependencies": {
|
|
42
49
|
"body-parser": "^2.2.1",
|
|
43
50
|
"chai": "^6.2.1",
|
|
44
51
|
"express": "^5.2.1",
|
|
45
|
-
"
|
|
52
|
+
"ink-testing-library": "^4.0.0",
|
|
53
|
+
"mocha": "^11.7.5",
|
|
54
|
+
"sinon": "^19.0.2"
|
|
46
55
|
}
|
|
47
56
|
}
|
package/samples/http.spec.yaml
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
tests:
|
|
2
|
-
- steps:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
2
|
+
- steps:
|
|
3
|
+
- loadVariables: env
|
|
4
|
+
- httpRequest:
|
|
5
|
+
url: http://localhost:8092/api/users
|
|
6
|
+
method: post
|
|
7
|
+
request:
|
|
8
|
+
body:
|
|
9
|
+
name: $USER
|
|
10
|
+
job: $JOB
|
|
11
|
+
response:
|
|
12
|
+
body:
|
|
13
|
+
name: John Doe
|
|
14
|
+
job: Software Engineer
|
|
15
|
+
- httpRequest:
|
|
16
|
+
url: http://localhost:8092/api/users
|
|
17
|
+
method: post
|
|
18
|
+
request:
|
|
19
|
+
body:
|
|
20
|
+
data:
|
|
21
|
+
- first_name: George
|
|
22
|
+
last_name: Bluth
|
|
23
|
+
id: 1
|
|
24
|
+
response:
|
|
25
|
+
body:
|
|
26
|
+
data:
|
|
27
|
+
- first_name: George
|
|
28
|
+
last_name: Bluth
|
|
29
|
+
variables:
|
|
30
|
+
ID: $$response.body.data[0].id
|
|
31
|
+
- httpRequest:
|
|
32
|
+
url: http://localhost:8092/api/$ID
|
|
33
|
+
method: get
|
|
34
|
+
timeout: 1000
|
|
35
|
+
savePath: response.json
|
|
36
|
+
maxVariation: 0
|
|
37
|
+
overwrite: aboveVariation
|
|
38
|
+
testId: foobar
|
|
39
|
+
specId: edf6cf93-04f4-4931-ae7d-037bd75def9a
|
|
@@ -3,26 +3,36 @@
|
|
|
3
3
|
{
|
|
4
4
|
"steps": [
|
|
5
5
|
{
|
|
6
|
-
"
|
|
7
|
-
|
|
6
|
+
"goTo": {
|
|
7
|
+
"url": "https://www.duckduckgo.com"
|
|
8
|
+
}
|
|
8
9
|
},
|
|
9
10
|
{
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
"find": {
|
|
12
|
+
"selector": "#searchbox_input",
|
|
13
|
+
"click": true,
|
|
14
|
+
"type": {
|
|
15
|
+
"keys": ["American Shorthair kittens", "$ENTER$"]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"variables": {}
|
|
16
19
|
},
|
|
17
20
|
{
|
|
18
|
-
"
|
|
19
|
-
|
|
21
|
+
"find": {
|
|
22
|
+
"selector": "[data-testid='web-vertical']"
|
|
23
|
+
},
|
|
24
|
+
"variables": {}
|
|
20
25
|
},
|
|
21
26
|
{
|
|
22
|
-
"
|
|
23
|
-
|
|
27
|
+
"screenshot": {
|
|
28
|
+
"path": "search-results.png",
|
|
29
|
+
"maxVariation": 0
|
|
30
|
+
}
|
|
24
31
|
}
|
|
25
|
-
]
|
|
32
|
+
],
|
|
33
|
+
"detectSteps": true,
|
|
34
|
+
"testId": "efd34931-ba4f-47be-85bb-d8cb61123b8d"
|
|
26
35
|
}
|
|
27
|
-
]
|
|
36
|
+
],
|
|
37
|
+
"specId": "02db96fc-7912-4eb6-ad79-9c96c2f1f8db"
|
|
28
38
|
}
|
|
@@ -70,11 +70,11 @@ function main() {
|
|
|
70
70
|
console.log(`Version mismatch detected. Setting version to: ${newVersion}`);
|
|
71
71
|
} else {
|
|
72
72
|
// Project version is already equal or greater than core version, just bump patch
|
|
73
|
+
newVersion = `${projMajor}.${projMinor}.${projPatch + 1}`;
|
|
73
74
|
console.log(
|
|
74
75
|
"Project version is current or ahead. Bumping patch version to:",
|
|
75
76
|
newVersion
|
|
76
77
|
);
|
|
77
|
-
newVersion = `${projMajor}.${projMinor}.${projPatch + 1}`;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Validate the new version before setting it
|
package/src/cli/App.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
const { createElement: h } = React;
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import TestRunner from './TestRunner.mjs';
|
|
6
|
+
import ResultsSummary from './ResultsSummary.mjs';
|
|
7
|
+
|
|
8
|
+
const App = ({ config, state }) => {
|
|
9
|
+
return h(Box, { flexDirection: 'column', paddingY: 1 },
|
|
10
|
+
h(Box, { marginBottom: 1 },
|
|
11
|
+
h(Text, { bold: true, color: 'cyan' }, 'Doc Detective')
|
|
12
|
+
),
|
|
13
|
+
|
|
14
|
+
state.phase === 'initializing' && h(Box, null,
|
|
15
|
+
h(Text, { color: 'gray' },
|
|
16
|
+
h(Spinner, { type: 'dots' }), ' Initializing...'
|
|
17
|
+
)
|
|
18
|
+
),
|
|
19
|
+
|
|
20
|
+
state.phase === 'running' && h(TestRunner, {
|
|
21
|
+
config,
|
|
22
|
+
progress: state.progress,
|
|
23
|
+
currentSpec: state.currentSpec,
|
|
24
|
+
currentTest: state.currentTest,
|
|
25
|
+
}),
|
|
26
|
+
|
|
27
|
+
state.phase === 'completed' && state.results && h(ResultsSummary, {
|
|
28
|
+
results: state.results,
|
|
29
|
+
config,
|
|
30
|
+
}),
|
|
31
|
+
|
|
32
|
+
state.phase === 'error' && h(Box, { flexDirection: 'column' },
|
|
33
|
+
h(Text, { color: 'red', bold: true }, '✖ Error'),
|
|
34
|
+
h(Text, { color: 'red' }, state.error?.message || String(state.error))
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default App;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
const { createElement: h } = React;
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
|
|
5
|
+
const ResultsSummary = ({ results, config }) => {
|
|
6
|
+
if (!results || !results.summary) {
|
|
7
|
+
return h(Box, null,
|
|
8
|
+
h(Text, { color: 'yellow' }, 'No results available.')
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { specs, tests, contexts, steps } = results.summary;
|
|
13
|
+
|
|
14
|
+
// Calculate totals
|
|
15
|
+
const totalSpecs = specs ? specs.pass + specs.fail + specs.warning + specs.skipped : 0;
|
|
16
|
+
const totalTests = tests ? tests.pass + tests.fail + tests.warning + tests.skipped : 0;
|
|
17
|
+
const totalContexts = contexts ? contexts.pass + contexts.fail + contexts.warning + contexts.skipped : 0;
|
|
18
|
+
const totalSteps = steps ? steps.pass + steps.fail + steps.warning + steps.skipped : 0;
|
|
19
|
+
|
|
20
|
+
// Check for failures
|
|
21
|
+
const hasFailures =
|
|
22
|
+
(specs && specs.fail > 0) ||
|
|
23
|
+
(tests && tests.fail > 0) ||
|
|
24
|
+
(contexts && contexts.fail > 0) ||
|
|
25
|
+
(steps && steps.fail > 0);
|
|
26
|
+
|
|
27
|
+
// Check if all skipped
|
|
28
|
+
const allSpecsSkipped =
|
|
29
|
+
specs && specs.pass === 0 && specs.fail === 0 && specs.skipped > 0;
|
|
30
|
+
|
|
31
|
+
const failedItems = hasFailures && results.specs ? getFailedItems(results) : [];
|
|
32
|
+
|
|
33
|
+
return h(Box, { flexDirection: 'column' },
|
|
34
|
+
// Header
|
|
35
|
+
h(Box, { marginBottom: 1 },
|
|
36
|
+
h(Text, { bold: true, underline: true }, 'Test Results Summary')
|
|
37
|
+
),
|
|
38
|
+
|
|
39
|
+
// Specs summary
|
|
40
|
+
specs && h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
41
|
+
h(Text, { bold: true }, 'Specs'),
|
|
42
|
+
h(Box, { marginLeft: 2 },
|
|
43
|
+
h(Text, null, `Total: ${totalSpecs} `),
|
|
44
|
+
specs.pass > 0 && h(Text, { color: 'green' }, `✓ ${specs.pass} passed `),
|
|
45
|
+
specs.fail > 0 && h(Text, { color: 'red' }, `✖ ${specs.fail} failed `),
|
|
46
|
+
specs.warning > 0 && h(Text, { color: 'yellow' }, `⚠ ${specs.warning} warnings `),
|
|
47
|
+
specs.skipped > 0 && h(Text, { color: 'gray' }, `⊘ ${specs.skipped} skipped`)
|
|
48
|
+
)
|
|
49
|
+
),
|
|
50
|
+
|
|
51
|
+
// Tests summary
|
|
52
|
+
tests && h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
53
|
+
h(Text, { bold: true }, 'Tests'),
|
|
54
|
+
h(Box, { marginLeft: 2 },
|
|
55
|
+
h(Text, null, `Total: ${totalTests} `),
|
|
56
|
+
tests.pass > 0 && h(Text, { color: 'green' }, `✓ ${tests.pass} passed `),
|
|
57
|
+
tests.fail > 0 && h(Text, { color: 'red' }, `✖ ${tests.fail} failed `),
|
|
58
|
+
tests.warning > 0 && h(Text, { color: 'yellow' }, `⚠ ${tests.warning} warnings `),
|
|
59
|
+
tests.skipped > 0 && h(Text, { color: 'gray' }, `⊘ ${tests.skipped} skipped`)
|
|
60
|
+
)
|
|
61
|
+
),
|
|
62
|
+
|
|
63
|
+
// Contexts summary
|
|
64
|
+
contexts && h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
65
|
+
h(Text, { bold: true }, 'Contexts'),
|
|
66
|
+
h(Box, { marginLeft: 2 },
|
|
67
|
+
h(Text, null, `Total: ${totalContexts} `),
|
|
68
|
+
contexts.pass > 0 && h(Text, { color: 'green' }, `✓ ${contexts.pass} passed `),
|
|
69
|
+
contexts.fail > 0 && h(Text, { color: 'red' }, `✖ ${contexts.fail} failed `),
|
|
70
|
+
contexts.warning > 0 && h(Text, { color: 'yellow' }, `⚠ ${contexts.warning} warnings `),
|
|
71
|
+
contexts.skipped > 0 && h(Text, { color: 'gray' }, `⊘ ${contexts.skipped} skipped`)
|
|
72
|
+
)
|
|
73
|
+
),
|
|
74
|
+
|
|
75
|
+
// Steps summary
|
|
76
|
+
steps && h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
77
|
+
h(Text, { bold: true }, 'Steps'),
|
|
78
|
+
h(Box, { marginLeft: 2 },
|
|
79
|
+
h(Text, null, `Total: ${totalSteps} `),
|
|
80
|
+
steps.pass > 0 && h(Text, { color: 'green' }, `✓ ${steps.pass} passed `),
|
|
81
|
+
steps.fail > 0 && h(Text, { color: 'red' }, `✖ ${steps.fail} failed `),
|
|
82
|
+
steps.warning > 0 && h(Text, { color: 'yellow' }, `⚠ ${steps.warning} warnings `),
|
|
83
|
+
steps.skipped > 0 && h(Text, { color: 'gray' }, `⊘ ${steps.skipped} skipped`)
|
|
84
|
+
)
|
|
85
|
+
),
|
|
86
|
+
|
|
87
|
+
// Overall status
|
|
88
|
+
h(Box, { marginTop: 1 },
|
|
89
|
+
allSpecsSkipped
|
|
90
|
+
? h(Text, { color: 'yellow' }, '⚠ All items were skipped')
|
|
91
|
+
: hasFailures
|
|
92
|
+
? h(Text, { color: 'red', bold: true }, '✖ Tests failed')
|
|
93
|
+
: h(Text, { color: 'green', bold: true }, '✓ All tests passed!')
|
|
94
|
+
),
|
|
95
|
+
|
|
96
|
+
// Failed items detail
|
|
97
|
+
hasFailures && results.specs && h(Box, { flexDirection: 'column', marginTop: 1 },
|
|
98
|
+
h(Text, { bold: true, color: 'red' }, 'Failed Items:'),
|
|
99
|
+
...failedItems.map((item, index) =>
|
|
100
|
+
h(Box, { key: index, marginLeft: 2 },
|
|
101
|
+
h(Text, { color: 'red' }, `• ${item}`)
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Helper function to extract failed items
|
|
109
|
+
function getFailedItems(results) {
|
|
110
|
+
const failures = [];
|
|
111
|
+
|
|
112
|
+
if (!results.specs) return failures;
|
|
113
|
+
|
|
114
|
+
results.specs.forEach((spec, specIndex) => {
|
|
115
|
+
if (spec.result === 'FAIL') {
|
|
116
|
+
failures.push(`Spec: ${spec.specId || `Spec ${specIndex + 1}`}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (spec.tests && spec.tests.length > 0) {
|
|
120
|
+
spec.tests.forEach((test, testIndex) => {
|
|
121
|
+
if (test.result === 'FAIL') {
|
|
122
|
+
failures.push(
|
|
123
|
+
`Test: ${test.testId || `Test ${testIndex + 1}`} (from ${
|
|
124
|
+
spec.specId || `Spec ${specIndex + 1}`
|
|
125
|
+
})`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (test.contexts && test.contexts.length > 0) {
|
|
130
|
+
test.contexts.forEach((context, contextIndex) => {
|
|
131
|
+
if (
|
|
132
|
+
context.result === 'FAIL' ||
|
|
133
|
+
(context.result && context.result.status === 'FAIL')
|
|
134
|
+
) {
|
|
135
|
+
failures.push(
|
|
136
|
+
`Context: ${context.platform || 'unknown'}/${
|
|
137
|
+
context.browser ? context.browser.name : 'unknown'
|
|
138
|
+
} (from ${test.testId || `Test ${testIndex + 1}`})`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (context.steps && context.steps.length > 0) {
|
|
143
|
+
context.steps.forEach((step, stepIndex) => {
|
|
144
|
+
if (step.result === 'FAIL') {
|
|
145
|
+
failures.push(
|
|
146
|
+
`Step: ${step.stepId || `Step ${stepIndex + 1}`} - ${
|
|
147
|
+
step.resultDescription || 'Unknown error'
|
|
148
|
+
}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return failures;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default ResultsSummary;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
const { createElement: h } = React;
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
|
|
6
|
+
const TestRunner = ({ config, progress, currentSpec, currentTest }) => {
|
|
7
|
+
return h(Box, { flexDirection: 'column' },
|
|
8
|
+
h(Box, { marginBottom: 1 },
|
|
9
|
+
h(Text, { color: 'yellow' },
|
|
10
|
+
h(Spinner, { type: 'dots' }), ' Running tests...'
|
|
11
|
+
)
|
|
12
|
+
),
|
|
13
|
+
|
|
14
|
+
// Progress bars
|
|
15
|
+
progress.specs.total > 0 && h(Box, { marginBottom: 0 },
|
|
16
|
+
h(Text, { color: 'gray' },
|
|
17
|
+
`Specs: ${progress.specs.current}/${progress.specs.total}`
|
|
18
|
+
)
|
|
19
|
+
),
|
|
20
|
+
|
|
21
|
+
progress.tests.total > 0 && h(Box, { marginBottom: 0 },
|
|
22
|
+
h(Text, { color: 'gray' },
|
|
23
|
+
`Tests: ${progress.tests.current}/${progress.tests.total}`
|
|
24
|
+
)
|
|
25
|
+
),
|
|
26
|
+
|
|
27
|
+
progress.steps.total > 0 && h(Box, { marginBottom: 1 },
|
|
28
|
+
h(Text, { color: 'gray' },
|
|
29
|
+
`Steps: ${progress.steps.current}/${progress.steps.total}`
|
|
30
|
+
)
|
|
31
|
+
),
|
|
32
|
+
|
|
33
|
+
// Current execution context
|
|
34
|
+
currentSpec && h(Box, { flexDirection: 'column', marginTop: 1 },
|
|
35
|
+
h(Text, { color: 'cyan' }, `Current: ${currentSpec}`),
|
|
36
|
+
currentTest && h(Text, { color: 'gray', dimColor: true }, `→ ${currentTest}`)
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default TestRunner;
|