doc-detective 1.2.2 → 1.2.3
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/README.md +3 -0
- package/package.json +3 -2
- package/sample/results.json +0 -60
- package/src/config.json +36 -0
- package/src/lib/analysis.js +2 -0
- package/src/lib/coverage.js +24 -22
- package/src/lib/suggest.js +6 -4
- package/src/lib/tests/goTo.js +3 -9
- package/src/lib/tests/httpRequest.js +72 -58
- package/src/lib/tests.js +15 -29
- package/src/lib/utils.js +50 -12
- package/sample/results.gif +0 -0
- package/sample/results.png +0 -0
package/README.md
CHANGED
|
@@ -298,6 +298,7 @@ Format:
|
|
|
298
298
|
| `responseHeaders` | Optional. A non-exhaustive object of headers that should be included in the response. If a specified header is missing, or if a specified header value is incorrect, the action fails. If the response headers include headers that aren't specified by this option, the test can still pass. <br> Supports setting the whole headers object and individual header values from environment variables. | <ul><li>`{ "Content-Type": "application/json" }`</li><li>`$RESPONSE_HEADERS`</li><li>`{ "Content-Type": "$CONTENT_TYPE" }`</li></ul> |
|
|
299
299
|
| `responseData` | Optional. A non-exhaustive data object that should be included in the response. If a specified data field is missing, or if a specified value is incorrect, the action fails. If the response data payload includes additional fields that aren't specified by this option, the test can still pass. <br> Supports setting the whole data object and individual data values from environment variables. | <ul><li>`{ "id": 1, "last_name": "Washington" }`</li><li>`"$REQUEST_DATA"`</li><li>`{ "id": 1, "last_name": "$LAST_NAME" }`</li></ul> |
|
|
300
300
|
| `statusCodes` | Optional. An array of accepted HTTP status response codes. Defaults to `[200]`. If the response's status code isn't included in this array, the action fails. | [ 200, 204 ] |
|
|
301
|
+
| `envsFromResponseData` | Optional. An array of environment variables to dynamically set from response data using [`jq`](https://stedolan.github.io/jq/) filters to select and transform the JSON data.<br><br>`name` values can contain uppercase letters, lowercase letters, numbers and underscores, but **don't** include a leading "$". | <code>[ { "name": "FIRST_NAME", "jqFilter": ".first_name" } ]</code>
|
|
301
302
|
|
|
302
303
|
### Check a link
|
|
303
304
|
|
|
@@ -363,6 +364,8 @@ Format:
|
|
|
363
364
|
|
|
364
365
|
## Analytics
|
|
365
366
|
|
|
367
|
+
**Note:** Analytics is currently turned off globally.
|
|
368
|
+
|
|
366
369
|
By default, Doc Detective doesn't collect any information about tests, devices, users, or documentation and doesn't check in with any external service or server. If you want to help inform decisions about the future development of Doc Detective—such as feature development and documentation creation—you can opt into sending anonymized analytics after you run tests at one of the multiple levels of detail.
|
|
367
370
|
|
|
368
371
|
There are multiple ways to turn on analytics:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doc-detective",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Unit test documentation (and record videos of those tests).",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"git": "^0.1.5",
|
|
33
33
|
"gnumake": "^0.3.5",
|
|
34
34
|
"n-readlines": "^1.0.1",
|
|
35
|
+
"node-jq": "^2.3.4",
|
|
35
36
|
"pixelmatch": "^5.3.0",
|
|
36
37
|
"pngjs": "^6.0.0",
|
|
37
38
|
"prompt-sync": "^4.2.0",
|
|
@@ -43,4 +44,4 @@
|
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"make": "^0.8.1"
|
|
45
46
|
}
|
|
46
|
-
}
|
|
47
|
+
}
|
package/sample/results.json
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
"tests": [
|
|
3
3
|
{
|
|
4
4
|
"id": "process-search-kittens",
|
|
5
|
-
"saveFailedTestRecordings": true,
|
|
6
|
-
"failedTestDirectory": "sample",
|
|
7
5
|
"actions": [
|
|
8
6
|
{
|
|
9
7
|
"action": "goTo",
|
|
@@ -13,49 +11,6 @@
|
|
|
13
11
|
"description": "Opened URI."
|
|
14
12
|
}
|
|
15
13
|
},
|
|
16
|
-
{
|
|
17
|
-
"action": "startRecording",
|
|
18
|
-
"overwrite": false,
|
|
19
|
-
"filename": "results.gif",
|
|
20
|
-
"fps": 15,
|
|
21
|
-
"result": {
|
|
22
|
-
"status": "PASS",
|
|
23
|
-
"description": "Started recording: /config/workspace/doc-detective/sample/temp_results.mp4",
|
|
24
|
-
"video": "/config/workspace/doc-detective/sample/temp_results.mp4"
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"action": "moveMouse",
|
|
29
|
-
"css": "#gbqfbb",
|
|
30
|
-
"alignH": "center",
|
|
31
|
-
"alignV": "center",
|
|
32
|
-
"offsetX": 0,
|
|
33
|
-
"offsetY": 0,
|
|
34
|
-
"result": {
|
|
35
|
-
"status": "PASS",
|
|
36
|
-
"description": "Moved mouse to element."
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"action": "wait",
|
|
41
|
-
"duration": "5000",
|
|
42
|
-
"result": {
|
|
43
|
-
"status": "PASS",
|
|
44
|
-
"description": "Wait complete."
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"action": "moveMouse",
|
|
49
|
-
"css": "[title=Search]",
|
|
50
|
-
"alignV": "center",
|
|
51
|
-
"alignH": "center",
|
|
52
|
-
"offsetX": 0,
|
|
53
|
-
"offsetY": 0,
|
|
54
|
-
"result": {
|
|
55
|
-
"status": "PASS",
|
|
56
|
-
"description": "Moved mouse to element."
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
14
|
{
|
|
60
15
|
"action": "type",
|
|
61
16
|
"css": "[title=Search]",
|
|
@@ -74,21 +29,6 @@
|
|
|
74
29
|
"description": "Wait complete."
|
|
75
30
|
}
|
|
76
31
|
},
|
|
77
|
-
{
|
|
78
|
-
"action": "scroll",
|
|
79
|
-
"y": 300,
|
|
80
|
-
"result": {
|
|
81
|
-
"status": "PASS",
|
|
82
|
-
"description": "Scroll complete."
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
"action": "stopRecording",
|
|
87
|
-
"result": {
|
|
88
|
-
"status": "PASS",
|
|
89
|
-
"description": "Stopped recording: /config/workspace/doc-detective/sample/results.gif"
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
32
|
{
|
|
93
33
|
"action": "screenshot",
|
|
94
34
|
"filename": "results.png",
|
package/src/config.json
CHANGED
|
@@ -35,47 +35,65 @@
|
|
|
35
35
|
"actionStatementClose": ")",
|
|
36
36
|
"markup": {
|
|
37
37
|
"onscreenText": {
|
|
38
|
+
"includeInCoverage": true,
|
|
39
|
+
"includeInSuggestions": true,
|
|
38
40
|
"regex": [
|
|
39
41
|
"\\*\\*.+?\\*\\*"
|
|
40
42
|
]
|
|
41
43
|
},
|
|
42
44
|
"emphasis": {
|
|
45
|
+
"includeInCoverage": true,
|
|
46
|
+
"includeInSuggestions": true,
|
|
43
47
|
"regex": [
|
|
44
48
|
"(?<!\\*)\\*(?!\\*).+?(?<!\\*)\\*(?!\\*)"
|
|
45
49
|
]
|
|
46
50
|
},
|
|
47
51
|
"image": {
|
|
52
|
+
"includeInCoverage": true,
|
|
53
|
+
"includeInSuggestions": true,
|
|
48
54
|
"regex": [
|
|
49
55
|
"!\\[.+?\\]\\(.+?\\)"
|
|
50
56
|
]
|
|
51
57
|
},
|
|
52
58
|
"hyperlink": {
|
|
59
|
+
"includeInCoverage": true,
|
|
60
|
+
"includeInSuggestions": true,
|
|
53
61
|
"regex": [
|
|
54
62
|
"(?<!!)\\[.+?\\]\\(.+?\\)"
|
|
55
63
|
]
|
|
56
64
|
},
|
|
57
65
|
"orderedList": {
|
|
66
|
+
"includeInCoverage": true,
|
|
67
|
+
"includeInSuggestions": true,
|
|
58
68
|
"regex": [
|
|
59
69
|
"(?<=\n) *?[0-9][0-9]?[0-9]?.\\s*.*"
|
|
60
70
|
]
|
|
61
71
|
},
|
|
62
72
|
"unorderedList": {
|
|
73
|
+
"includeInCoverage": true,
|
|
74
|
+
"includeInSuggestions": true,
|
|
63
75
|
"regex": [
|
|
64
76
|
"(?<=\n) *?\\*.\\s*.*",
|
|
65
77
|
"(?<=\n) *?-.\\s*.*"
|
|
66
78
|
]
|
|
67
79
|
},
|
|
68
80
|
"codeInline": {
|
|
81
|
+
"includeInCoverage": true,
|
|
82
|
+
"includeInSuggestions": true,
|
|
69
83
|
"regex": [
|
|
70
84
|
"(?<!`)`(?!`).+?(?<!`)`(?!`)"
|
|
71
85
|
]
|
|
72
86
|
},
|
|
73
87
|
"codeBlock": {
|
|
88
|
+
"includeInCoverage": true,
|
|
89
|
+
"includeInSuggestions": true,
|
|
74
90
|
"regex": [
|
|
75
91
|
"(?=(```))(\\w|\\W)*(?<=```)"
|
|
76
92
|
]
|
|
77
93
|
},
|
|
78
94
|
"interaction": {
|
|
95
|
+
"includeInCoverage": true,
|
|
96
|
+
"includeInSuggestions": true,
|
|
79
97
|
"regex": [
|
|
80
98
|
"[cC]lick",
|
|
81
99
|
"[tT]ap",
|
|
@@ -106,46 +124,64 @@
|
|
|
106
124
|
"actionStatementClose": "-->",
|
|
107
125
|
"markup": {
|
|
108
126
|
"onscreenText": {
|
|
127
|
+
"includeInCoverage": true,
|
|
128
|
+
"includeInSuggestions": true,
|
|
109
129
|
"regex": [
|
|
110
130
|
"(?=(<b))(\\w|\\W)*(?<=<\/b>)"
|
|
111
131
|
]
|
|
112
132
|
},
|
|
113
133
|
"emphasis": {
|
|
134
|
+
"includeInCoverage": true,
|
|
135
|
+
"includeInSuggestions": true,
|
|
114
136
|
"regex": [
|
|
115
137
|
"(?=(<i))(\\w|\\W)*(?<=<\/i>)"
|
|
116
138
|
]
|
|
117
139
|
},
|
|
118
140
|
"image": {
|
|
141
|
+
"includeInCoverage": true,
|
|
142
|
+
"includeInSuggestions": true,
|
|
119
143
|
"regex": [
|
|
120
144
|
"(?=(<img))(\\w|\\W)*(?<=<\/img>|>)"
|
|
121
145
|
]
|
|
122
146
|
},
|
|
123
147
|
"hyperlink": {
|
|
148
|
+
"includeInCoverage": true,
|
|
149
|
+
"includeInSuggestions": true,
|
|
124
150
|
"regex": [
|
|
125
151
|
"(?=(<a))(\\w|\\W)*(?<=<\/a>)"
|
|
126
152
|
]
|
|
127
153
|
},
|
|
128
154
|
"orderedList": {
|
|
155
|
+
"includeInCoverage": true,
|
|
156
|
+
"includeInSuggestions": true,
|
|
129
157
|
"regex": [
|
|
130
158
|
"(?=(<ol))(\\w|\\W)*(?<=<\/ol>)"
|
|
131
159
|
]
|
|
132
160
|
},
|
|
133
161
|
"unorderedList": {
|
|
162
|
+
"includeInCoverage": true,
|
|
163
|
+
"includeInSuggestions": true,
|
|
134
164
|
"regex": [
|
|
135
165
|
"(?=(<ul))(\\w|\\W)*(?<=<\/ul>)"
|
|
136
166
|
]
|
|
137
167
|
},
|
|
138
168
|
"codeInline": {
|
|
169
|
+
"includeInCoverage": true,
|
|
170
|
+
"includeInSuggestions": true,
|
|
139
171
|
"regex": [
|
|
140
172
|
"(?=(<code))(\\w|\\W)*(?<=<\/code>)"
|
|
141
173
|
]
|
|
142
174
|
},
|
|
143
175
|
"codeBlock": {
|
|
176
|
+
"includeInCoverage": true,
|
|
177
|
+
"includeInSuggestions": true,
|
|
144
178
|
"regex": [
|
|
145
179
|
"(?=(<pre))(\\w|\\W)*(?<=<\/pre>)"
|
|
146
180
|
]
|
|
147
181
|
},
|
|
148
182
|
"interaction": {
|
|
183
|
+
"includeInCoverage": true,
|
|
184
|
+
"includeInSuggestions": true,
|
|
149
185
|
"regex": [
|
|
150
186
|
"[cC]lick",
|
|
151
187
|
"[tT]ap",
|
package/src/lib/analysis.js
CHANGED
|
@@ -204,6 +204,8 @@ function checkMarkupCoverage(config, testCoverage) {
|
|
|
204
204
|
// Only keep marks that have a truthy (>0) length
|
|
205
205
|
Object.keys(markup).forEach((mark) => {
|
|
206
206
|
markCoverage = {
|
|
207
|
+
includeInCoverage: markup[mark].includeInCoverage,
|
|
208
|
+
includeInSuggestions: markup[mark].includeInSuggestions,
|
|
207
209
|
coveredLines: [],
|
|
208
210
|
coveredMatches: [],
|
|
209
211
|
uncoveredLines: [],
|
package/src/lib/coverage.js
CHANGED
|
@@ -21,30 +21,32 @@ function reportCoverage(config, markupCoverage) {
|
|
|
21
21
|
uncovered: 0,
|
|
22
22
|
};
|
|
23
23
|
Object.keys(file.markup).forEach((mark) => {
|
|
24
|
-
if (
|
|
25
|
-
report.summary[mark]
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
if (file.markup[mark].includeInCoverage) {
|
|
25
|
+
if (typeof report.summary[mark] === "undefined") {
|
|
26
|
+
report.summary[mark] = {
|
|
27
|
+
covered: 0,
|
|
28
|
+
uncovered: 0,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
report.summary.covered =
|
|
32
|
+
report.summary.covered + file.markup[mark].coveredLines.length;
|
|
33
|
+
report.summary.uncovered =
|
|
34
|
+
report.summary.uncovered + file.markup[mark].uncoveredLines.length;
|
|
35
|
+
report.summary[mark].covered =
|
|
36
|
+
report.summary[mark].covered + file.markup[mark].coveredLines.length;
|
|
37
|
+
report.summary[mark].uncovered =
|
|
38
|
+
report.summary[mark].uncovered +
|
|
39
|
+
file.markup[mark].uncoveredLines.length;
|
|
40
|
+
fileJson.covered =
|
|
41
|
+
fileJson.covered + file.markup[mark].coveredLines.length;
|
|
42
|
+
fileJson.uncovered =
|
|
43
|
+
fileJson.uncovered + file.markup[mark].uncoveredLines.length;
|
|
44
|
+
fileJson[mark] = {
|
|
45
|
+
covered: file.markup[mark].coveredLines.length,
|
|
46
|
+
uncovered: file.markup[mark].uncoveredLines.length,
|
|
47
|
+
uncoveredMatches: file.markup[mark].uncoveredMatches,
|
|
28
48
|
};
|
|
29
49
|
}
|
|
30
|
-
report.summary.covered =
|
|
31
|
-
report.summary.covered + file.markup[mark].coveredLines.length;
|
|
32
|
-
report.summary.uncovered =
|
|
33
|
-
report.summary.uncovered + file.markup[mark].uncoveredLines.length;
|
|
34
|
-
report.summary[mark].covered =
|
|
35
|
-
report.summary[mark].covered + file.markup[mark].coveredLines.length;
|
|
36
|
-
report.summary[mark].uncovered =
|
|
37
|
-
report.summary[mark].uncovered +
|
|
38
|
-
file.markup[mark].uncoveredLines.length;
|
|
39
|
-
fileJson.covered =
|
|
40
|
-
fileJson.covered + file.markup[mark].coveredLines.length;
|
|
41
|
-
fileJson.uncovered =
|
|
42
|
-
fileJson.uncovered + file.markup[mark].uncoveredLines.length;
|
|
43
|
-
fileJson[mark] = {
|
|
44
|
-
covered: file.markup[mark].coveredLines.length,
|
|
45
|
-
uncovered: file.markup[mark].uncoveredLines.length,
|
|
46
|
-
uncoveredMatches: file.markup[mark].uncoveredMatches,
|
|
47
|
-
};
|
|
48
50
|
});
|
|
49
51
|
report.files.push(fileJson);
|
|
50
52
|
});
|
package/src/lib/suggest.js
CHANGED
|
@@ -599,10 +599,12 @@ function transformMatches(fileMarkupObject) {
|
|
|
599
599
|
matches = [];
|
|
600
600
|
// Load array with uncovered matches
|
|
601
601
|
Object.keys(fileMarkupObject).forEach((mark) => {
|
|
602
|
-
fileMarkupObject[mark].
|
|
603
|
-
match
|
|
604
|
-
|
|
605
|
-
|
|
602
|
+
if (fileMarkupObject[mark].includeInSuggestions) {
|
|
603
|
+
fileMarkupObject[mark].uncoveredMatches.forEach((match) => {
|
|
604
|
+
match.type = mark;
|
|
605
|
+
matches.push(match);
|
|
606
|
+
});
|
|
607
|
+
}
|
|
606
608
|
});
|
|
607
609
|
// Sort matches by line, then index
|
|
608
610
|
matches.sort((a, b) => a.line - b.line || a.indexInFile - b.indexInFile);
|
package/src/lib/tests/goTo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { setEnvs } = require("../utils");
|
|
1
|
+
const { setEnvs, loadEnvs } = require("../utils");
|
|
2
2
|
|
|
3
3
|
exports.goTo = goTo;
|
|
4
4
|
|
|
@@ -17,14 +17,8 @@ async function goTo(action, page) {
|
|
|
17
17
|
let result = await setEnvs(action.env);
|
|
18
18
|
if (result.status === "FAIL") return { result };
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
process.env[action.uri.substring(1)] != undefined
|
|
23
|
-
) {
|
|
24
|
-
uri = process.env[action.uri.substring(1)];
|
|
25
|
-
} else {
|
|
26
|
-
uri = action.uri;
|
|
27
|
-
}
|
|
20
|
+
uri = loadEnvs(action.uri);
|
|
21
|
+
|
|
28
22
|
// Catch common formatting errors
|
|
29
23
|
if (!uri.includes("://")) uri = "https://" + uri;
|
|
30
24
|
// Run action
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const axios = require("axios");
|
|
2
|
-
const
|
|
2
|
+
const jq = require("node-jq");
|
|
3
|
+
const { exit } = require("process");
|
|
4
|
+
const { setEnvs, loadEnvs, log } = require("../utils");
|
|
3
5
|
|
|
4
6
|
exports.httpRequest = httpRequest;
|
|
5
7
|
|
|
6
|
-
async function httpRequest(action) {
|
|
8
|
+
async function httpRequest(action, config) {
|
|
7
9
|
const methods = ["get", "post", "put", "patch", "delete"];
|
|
8
10
|
|
|
9
11
|
let status;
|
|
@@ -23,6 +25,7 @@ async function httpRequest(action) {
|
|
|
23
25
|
responseHeaders: {},
|
|
24
26
|
responseData: {},
|
|
25
27
|
statusCodes: ["200"],
|
|
28
|
+
envsFromResponseData: [],
|
|
26
29
|
};
|
|
27
30
|
|
|
28
31
|
// Load environment variables
|
|
@@ -33,11 +36,8 @@ async function httpRequest(action) {
|
|
|
33
36
|
|
|
34
37
|
// URI
|
|
35
38
|
//// Define
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
} else {
|
|
39
|
-
uri = action.uri || defaultPayload.uri;
|
|
40
|
-
}
|
|
39
|
+
uri = loadEnvs(action.uri) || defaultPayload.uri;
|
|
40
|
+
|
|
41
41
|
//// Validate
|
|
42
42
|
if (!uri || typeof uri != "string") {
|
|
43
43
|
//Fail
|
|
@@ -50,11 +50,8 @@ async function httpRequest(action) {
|
|
|
50
50
|
|
|
51
51
|
// Method
|
|
52
52
|
//// Define
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} else {
|
|
56
|
-
method = action.method || defaultPayload.method;
|
|
57
|
-
}
|
|
53
|
+
method = loadEnvs(action.method) || defaultPayload.method;
|
|
54
|
+
|
|
58
55
|
//// Sanitize
|
|
59
56
|
method = method.toLowerCase();
|
|
60
57
|
//// Validate
|
|
@@ -71,16 +68,10 @@ async function httpRequest(action) {
|
|
|
71
68
|
// Headers
|
|
72
69
|
if (action.headers && JSON.stringify(action.headers) != "{}") {
|
|
73
70
|
//// Define
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
headers = JSON.parse(headers);
|
|
78
|
-
} catch {}
|
|
79
|
-
} else {
|
|
80
|
-
headers = action.headers || defaultPayload.headers;
|
|
81
|
-
}
|
|
71
|
+
headers = loadEnvs(action.headers) || defaultPayload.headers;
|
|
72
|
+
|
|
82
73
|
//// Load environment variables
|
|
83
|
-
headers =
|
|
74
|
+
headers = loadEnvs(headers);
|
|
84
75
|
//// Validate
|
|
85
76
|
//// Set request
|
|
86
77
|
if (JSON.stringify(headers) != "{}") request.headers = headers;
|
|
@@ -89,16 +80,10 @@ async function httpRequest(action) {
|
|
|
89
80
|
// Params
|
|
90
81
|
if (action.params && JSON.stringify(action.params) != "{}") {
|
|
91
82
|
//// Define
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
params = JSON.parse(params);
|
|
96
|
-
} catch {}
|
|
97
|
-
} else {
|
|
98
|
-
params = action.params || defaultPayload.params;
|
|
99
|
-
}
|
|
83
|
+
params = loadEnvs(action.params) || defaultPayload.params;
|
|
84
|
+
|
|
100
85
|
//// Load environment variables
|
|
101
|
-
params =
|
|
86
|
+
params = loadEnvs(params);
|
|
102
87
|
//// Validate
|
|
103
88
|
//// Set request
|
|
104
89
|
if (params != {}) request.params = params;
|
|
@@ -107,16 +92,10 @@ async function httpRequest(action) {
|
|
|
107
92
|
// requestData
|
|
108
93
|
if (action.requestData) {
|
|
109
94
|
//// Define
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
requestData = JSON.parse(requestData);
|
|
114
|
-
} catch {}
|
|
115
|
-
} else {
|
|
116
|
-
requestData = action.requestData || defaultPayload.requestData;
|
|
117
|
-
}
|
|
95
|
+
requestData = loadEnvs(action.requestData) || defaultPayload.requestData;
|
|
96
|
+
|
|
118
97
|
//// Load environment variables
|
|
119
|
-
requestData =
|
|
98
|
+
requestData = loadEnvs(requestData);
|
|
120
99
|
//// Validate
|
|
121
100
|
//// Set request
|
|
122
101
|
if (requestData != {}) request.data = requestData;
|
|
@@ -124,30 +103,19 @@ async function httpRequest(action) {
|
|
|
124
103
|
|
|
125
104
|
// responseData
|
|
126
105
|
//// Define
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
responseData = JSON.parse(responseData);
|
|
131
|
-
} catch {}
|
|
132
|
-
} else {
|
|
133
|
-
responseData = action.responseData || defaultPayload.responseData;
|
|
134
|
-
}
|
|
106
|
+
responseData = loadEnvs(action.responseData) || defaultPayload.responseData;
|
|
107
|
+
|
|
135
108
|
//// Load environment variables
|
|
136
|
-
responseData =
|
|
109
|
+
responseData = loadEnvs(responseData);
|
|
137
110
|
//// Validate
|
|
138
111
|
|
|
139
112
|
// responseHeaders
|
|
140
113
|
//// Define
|
|
141
|
-
|
|
142
|
-
responseHeaders
|
|
143
|
-
|
|
144
|
-
responseHeaders = JSON.parse(responseHeaders);
|
|
145
|
-
} catch {}
|
|
146
|
-
} else {
|
|
147
|
-
responseHeaders = action.responseHeaders || defaultPayload.responseHeaders;
|
|
148
|
-
}
|
|
114
|
+
responseHeaders =
|
|
115
|
+
loadEnvs(action.responseHeaders) || defaultPayload.responseHeaders;
|
|
116
|
+
|
|
149
117
|
//// Load environment variables
|
|
150
|
-
responseHeaders =
|
|
118
|
+
responseHeaders = loadEnvs(responseHeaders);
|
|
151
119
|
//// Validate
|
|
152
120
|
|
|
153
121
|
// Status codes
|
|
@@ -161,6 +129,34 @@ async function httpRequest(action) {
|
|
|
161
129
|
//// Validate
|
|
162
130
|
if (statusCodes === []) statusCodes = defaultPayload.statusCodes;
|
|
163
131
|
|
|
132
|
+
// Envs from response data
|
|
133
|
+
//// Define
|
|
134
|
+
envsFromResponseData =
|
|
135
|
+
action.envsFromResponseData || defaultPayload.envsFromResponseData;
|
|
136
|
+
//// Sanitize
|
|
137
|
+
for (i = 0; i < envsFromResponseData.length; i++) {
|
|
138
|
+
if (typeof statusCodes[i] === "string")
|
|
139
|
+
statusCodes[i] = Number(statusCodes[i]);
|
|
140
|
+
}
|
|
141
|
+
//// Validate
|
|
142
|
+
let validEnvs = envsFromResponseData.every(
|
|
143
|
+
(env) =>
|
|
144
|
+
typeof env === "object" &&
|
|
145
|
+
env.name.match(/^[a-zA-Z0-9_]+$/gm) &&
|
|
146
|
+
typeof env.jqFilter === "string" &&
|
|
147
|
+
env.jqFilter.length > 1
|
|
148
|
+
);
|
|
149
|
+
if (!validEnvs) {
|
|
150
|
+
envsFromResponseData = [];
|
|
151
|
+
log(
|
|
152
|
+
config,
|
|
153
|
+
"warning",
|
|
154
|
+
"Not setting environment variables. One or more invalid variable definitions."
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (envsFromResponseData === [])
|
|
158
|
+
envsFromResponseData = defaultPayload.envsFromResponseData;
|
|
159
|
+
|
|
164
160
|
// Send request
|
|
165
161
|
response = await axios(request)
|
|
166
162
|
.then((response) => {
|
|
@@ -211,12 +207,30 @@ async function httpRequest(action) {
|
|
|
211
207
|
description =
|
|
212
208
|
description +
|
|
213
209
|
` Expected response headers were present in actual response headers.`;
|
|
214
|
-
} else {
|
|
215
210
|
status = "FAIL";
|
|
211
|
+
} else {
|
|
216
212
|
description = description + " " + dataComparison.result.description;
|
|
217
213
|
}
|
|
218
214
|
}
|
|
219
215
|
|
|
216
|
+
// Set environment variables from response data
|
|
217
|
+
for (const variable of envsFromResponseData) {
|
|
218
|
+
let value = await jq.run(variable.jqFilter, response.data, {
|
|
219
|
+
input: "json",
|
|
220
|
+
output: "compact",
|
|
221
|
+
});
|
|
222
|
+
if (value) {
|
|
223
|
+
process.env[variable.name] = value;
|
|
224
|
+
description =
|
|
225
|
+
description + ` Set '$${variable.name}' environment variable.`;
|
|
226
|
+
} else {
|
|
227
|
+
if (status != "FAIL") status = "WARNING";
|
|
228
|
+
description =
|
|
229
|
+
description +
|
|
230
|
+
` Couldn't set '${variable.name}' environment variable. The jq filter (${variable.jqFilter}) returned a null result.`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
220
234
|
description = description.trim();
|
|
221
235
|
result = { status, description };
|
|
222
236
|
return { result };
|
package/src/lib/tests.js
CHANGED
|
@@ -2,7 +2,7 @@ const puppeteer = require("puppeteer");
|
|
|
2
2
|
const fs = require("fs");
|
|
3
3
|
const { exit, stdout, exitCode } = require("process");
|
|
4
4
|
const { installMouseHelper } = require("./install-mouse-helper");
|
|
5
|
-
const { setEnvs, log, timestamp } = require("./utils");
|
|
5
|
+
const { setEnvs, log, timestamp, loadEnvs } = require("./utils");
|
|
6
6
|
const util = require("util");
|
|
7
7
|
const exec = util.promisify(require("child_process").exec);
|
|
8
8
|
const axios = require("axios");
|
|
@@ -320,7 +320,7 @@ async function runAction(config, action, page, videoDetails) {
|
|
|
320
320
|
result = await checkLink(action);
|
|
321
321
|
break;
|
|
322
322
|
case "httpRequest":
|
|
323
|
-
result = await httpRequest(action);
|
|
323
|
+
result = await httpRequest(action, config);
|
|
324
324
|
break;
|
|
325
325
|
}
|
|
326
326
|
return result;
|
|
@@ -337,14 +337,7 @@ async function checkLink(action) {
|
|
|
337
337
|
let result = await setEnvs(action.env);
|
|
338
338
|
if (result.status === "FAIL") return { result };
|
|
339
339
|
}
|
|
340
|
-
|
|
341
|
-
action.uri[0] === "$" &&
|
|
342
|
-
process.env[action.uri.substring(1)] != undefined
|
|
343
|
-
) {
|
|
344
|
-
uri = process.env[action.uri.substring(1)];
|
|
345
|
-
} else {
|
|
346
|
-
uri = action.uri;
|
|
347
|
-
}
|
|
340
|
+
uri = loadEnvs(action.uri);
|
|
348
341
|
|
|
349
342
|
// Validate protocol
|
|
350
343
|
if (uri.indexOf("://") < 0) {
|
|
@@ -393,15 +386,20 @@ async function runShell(action) {
|
|
|
393
386
|
let description;
|
|
394
387
|
let result;
|
|
395
388
|
let exitCode;
|
|
389
|
+
let command;
|
|
396
390
|
|
|
397
|
-
//
|
|
391
|
+
// Set environment variables
|
|
398
392
|
if (action.env) {
|
|
399
393
|
let result = await setEnvs(action.env);
|
|
400
394
|
if (result.status === "FAIL") return { result };
|
|
401
395
|
}
|
|
402
396
|
|
|
397
|
+
// Command
|
|
398
|
+
//// Load envs
|
|
399
|
+
command = loadEnvs(action.command);
|
|
400
|
+
|
|
403
401
|
// Promisify and execute command
|
|
404
|
-
const promise = exec(
|
|
402
|
+
const promise = exec(command);
|
|
405
403
|
const child = promise.child;
|
|
406
404
|
child.on("close", function (code) {
|
|
407
405
|
exitCode = code;
|
|
@@ -474,15 +472,9 @@ async function typeElement(action, elementHandle) {
|
|
|
474
472
|
}
|
|
475
473
|
// Type keys
|
|
476
474
|
if (action.keys) {
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
process.env[action.keys.substring(1)] != undefined
|
|
481
|
-
) {
|
|
482
|
-
keys = process.env[action.keys.substring(1)];
|
|
483
|
-
} else {
|
|
484
|
-
keys = action.keys;
|
|
485
|
-
}
|
|
475
|
+
// Resolve environment variables in keys
|
|
476
|
+
keys = loadEnvs(action.keys);
|
|
477
|
+
|
|
486
478
|
try {
|
|
487
479
|
await elementHandle.type(keys);
|
|
488
480
|
} catch {
|
|
@@ -547,14 +539,8 @@ async function matchText(action, page) {
|
|
|
547
539
|
if (result.status === "FAIL") return { result };
|
|
548
540
|
}
|
|
549
541
|
// Set text
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
process.env[action.text.substring(1)] != undefined
|
|
553
|
-
) {
|
|
554
|
-
text = process.env[action.text.substring(1)];
|
|
555
|
-
} else {
|
|
556
|
-
text = action.text;
|
|
557
|
-
}
|
|
542
|
+
text = loadEnvs(action.text);
|
|
543
|
+
|
|
558
544
|
let elementTag = await page.$eval(action.css, (element) =>
|
|
559
545
|
element.tagName.toLowerCase()
|
|
560
546
|
);
|
package/src/lib/utils.js
CHANGED
|
@@ -17,6 +17,7 @@ exports.setEnvs = setEnvs;
|
|
|
17
17
|
exports.loadEnvsForObject = loadEnvsForObject;
|
|
18
18
|
exports.log = log;
|
|
19
19
|
exports.timestamp = timestamp;
|
|
20
|
+
exports.loadEnvs = loadEnvs;
|
|
20
21
|
|
|
21
22
|
const analyticsRequest =
|
|
22
23
|
"Thanks for using Doc Detective! If you want to contribute to the project, consider sending analytics to help us understand usage patterns and functional gaps. To turn on analytics, set 'analytics.send = true' in your config, or use the '-a true' argument. See https://github.com/hawkeyexl/doc-detective#analytics";
|
|
@@ -1014,23 +1015,60 @@ async function log(config, level, message) {
|
|
|
1014
1015
|
}
|
|
1015
1016
|
}
|
|
1016
1017
|
|
|
1017
|
-
function
|
|
1018
|
-
|
|
1018
|
+
function loadEnvs(stringOrObject) {
|
|
1019
|
+
if (!stringOrObject) return stringOrObject;
|
|
1020
|
+
// Try to convert string to object
|
|
1021
|
+
try {
|
|
1019
1022
|
if (
|
|
1020
|
-
|
|
1021
|
-
|
|
1023
|
+
typeof stringOrObject === "string" &&
|
|
1024
|
+
typeof JSON.parse(stringOrObject) === "object"
|
|
1022
1025
|
) {
|
|
1023
|
-
|
|
1026
|
+
stringOrObject = JSON.parse(stringOrObject);
|
|
1024
1027
|
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1028
|
+
} catch {}
|
|
1029
|
+
if (typeof stringOrObject === "object") {
|
|
1030
|
+
// Load for object
|
|
1031
|
+
stringOrObject = loadEnvsForObject(stringOrObject);
|
|
1032
|
+
} else {
|
|
1033
|
+
// Load for string
|
|
1034
|
+
stringOrObject = loadEnvsForString(stringOrObject);
|
|
1035
|
+
}
|
|
1036
|
+
// Try to convert resolved string to object
|
|
1037
|
+
try {
|
|
1038
|
+
if (typeof JSON.parse(stringOrObject) === "object") {
|
|
1039
|
+
stringOrObject = JSON.parse(stringOrObject);
|
|
1040
|
+
}
|
|
1041
|
+
} catch {}
|
|
1042
|
+
return stringOrObject;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function loadEnvsForString(string) {
|
|
1046
|
+
// Find all variables
|
|
1047
|
+
variableRegex = new RegExp(/\$[a-zA-Z0-9_]+/, "g");
|
|
1048
|
+
matches = string.match(variableRegex);
|
|
1049
|
+
// If no matches, return
|
|
1050
|
+
if (!matches) return string;
|
|
1051
|
+
// Iterate matches
|
|
1052
|
+
matches.forEach((match) => {
|
|
1053
|
+
// Check if is declared variable
|
|
1054
|
+
value = process.env[match.substring(1)];
|
|
1055
|
+
if (value) {
|
|
1056
|
+
// If variable value might have a nested variable, recurse to try to resolve
|
|
1057
|
+
if (value.includes("$")) value = loadEnvs(value);
|
|
1058
|
+
// Convert to string in case value was a substring of the greater string
|
|
1059
|
+
if (typeof value === "object") value = JSON.stringify(value);
|
|
1060
|
+
// Replace match with variable value
|
|
1061
|
+
string = string.replace(match, value);
|
|
1032
1062
|
}
|
|
1033
1063
|
});
|
|
1064
|
+
return string;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function loadEnvsForObject(object) {
|
|
1068
|
+
Object.keys(object).forEach((key) => {
|
|
1069
|
+
// Resolve all variables in key value
|
|
1070
|
+
object[key] = loadEnvs(object[key]);
|
|
1071
|
+
});
|
|
1034
1072
|
return object;
|
|
1035
1073
|
}
|
|
1036
1074
|
|
package/sample/results.gif
DELETED
|
Binary file
|
package/sample/results.png
DELETED
|
Binary file
|