doc-detective 1.2.2 → 1.2.4

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 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/icon.png ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc-detective",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
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
+ }
@@ -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/index.js CHANGED
@@ -13,11 +13,12 @@ const { reportCoverage } = require("./lib/coverage");
13
13
  const { suggestTests, runSuggestions } = require("./lib/suggest");
14
14
  const { exit } = require("process");
15
15
 
16
- exports.run = main;
16
+ exports.run = test;
17
+ exports.test = test;
17
18
  exports.coverage = coverage;
18
19
  exports.suggest = suggest;
19
20
 
20
- async function main(config, argv) {
21
+ async function test(config, argv) {
21
22
  // Set args
22
23
  argv = setArgs(argv);
23
24
  log(config, "debug", `ARGV:`);
@@ -50,6 +51,8 @@ async function main(config, argv) {
50
51
  if (config.analytics.send) {
51
52
  // sendAnalytics(config, results);
52
53
  }
54
+
55
+ return results;
53
56
  }
54
57
 
55
58
  async function coverage(config, argv) {
@@ -82,6 +85,8 @@ async function coverage(config, argv) {
82
85
 
83
86
  // Output
84
87
  outputResults(config.coverageOutput, coverageReport, config);
88
+
89
+ return coverageReport;
85
90
  }
86
91
 
87
92
  async function suggest(config, argv) {
@@ -120,4 +125,6 @@ async function suggest(config, argv) {
120
125
  suggestionReport,
121
126
  config
122
127
  );
128
+
129
+ return suggestionReport;
123
130
  }
@@ -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: [],
@@ -21,30 +21,32 @@ function reportCoverage(config, markupCoverage) {
21
21
  uncovered: 0,
22
22
  };
23
23
  Object.keys(file.markup).forEach((mark) => {
24
- if (typeof report.summary[mark] === "undefined") {
25
- report.summary[mark] = {
26
- covered: 0,
27
- uncovered: 0,
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
  });
@@ -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].uncoveredMatches.forEach((match) => {
603
- match.type = mark;
604
- matches.push(match);
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);
@@ -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
- if (
21
- action.uri[0] === "$" &&
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 { setEnvs, loadEnvsForObject } = require("../utils");
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;
@@ -17,12 +19,13 @@ async function httpRequest(action) {
17
19
  let defaultPayload = {
18
20
  uri: "",
19
21
  method: "get",
20
- headers: {},
21
- params: {},
22
+ requestHeaders: {},
23
+ requestParams: {},
22
24
  requestData: {},
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
- if (action.uri[0] === "$") {
37
- uri = process.env[action.uri.substring(1)];
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
- if (action.method && action.method[0] === "$") {
54
- method = process.env[action.method.substring(1)];
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
@@ -69,54 +66,40 @@ async function httpRequest(action) {
69
66
  request.method = method;
70
67
 
71
68
  // Headers
72
- if (action.headers && JSON.stringify(action.headers) != "{}") {
69
+ if (
70
+ (action.requestHeaders && JSON.stringify(action.requestHeaders) != "{}") ||
71
+ (action.headers && JSON.stringify(action.headers) != "{}")
72
+ ) {
73
73
  //// Define
74
- if (action.headers[0] === "$") {
75
- headers = process.env[action.headers.substring(1)];
76
- try {
77
- headers = JSON.parse(headers);
78
- } catch {}
79
- } else {
80
- headers = action.headers || defaultPayload.headers;
81
- }
82
- //// Load environment variables
83
- headers = loadEnvsForObject(headers);
74
+ requestHeaders =
75
+ loadEnvs(action.requestHeaders) ||
76
+ loadEnvs(action.headers) ||
77
+ defaultPayload.requestHeaders;
78
+
84
79
  //// Validate
85
80
  //// Set request
86
- if (JSON.stringify(headers) != "{}") request.headers = headers;
81
+ if (JSON.stringify(requestHeaders) != "{}")
82
+ request.headers = requestHeaders;
87
83
  }
88
84
 
89
85
  // Params
90
- if (action.params && JSON.stringify(action.params) != "{}") {
86
+ if ((action.requestParams && JSON.stringify(action.requestParams) != "{}") || (action.params && JSON.stringify(action.params) != "{}")) {
91
87
  //// Define
92
- if (action.params[0] === "$") {
93
- params = process.env[action.params.substring(1)];
94
- try {
95
- params = JSON.parse(params);
96
- } catch {}
97
- } else {
98
- params = action.params || defaultPayload.params;
99
- }
100
- //// Load environment variables
101
- params = loadEnvsForObject(params);
88
+ requestParams =
89
+ loadEnvs(action.requestParams) ||
90
+ loadEnvs(action.params) ||
91
+ defaultPayload.requestParams;
92
+
102
93
  //// Validate
103
94
  //// Set request
104
- if (params != {}) request.params = params;
95
+ if (JSON.stringify(requestParams) != "{}") request.params = requestParams;
105
96
  }
106
97
 
107
98
  // requestData
108
99
  if (action.requestData) {
109
100
  //// Define
110
- if (action.requestData[0] === "$") {
111
- requestData = process.env[action.requestData.substring(1)];
112
- try {
113
- requestData = JSON.parse(requestData);
114
- } catch {}
115
- } else {
116
- requestData = action.requestData || defaultPayload.requestData;
117
- }
118
- //// Load environment variables
119
- requestData = loadEnvsForObject(requestData);
101
+ requestData = loadEnvs(action.requestData) || defaultPayload.requestData;
102
+
120
103
  //// Validate
121
104
  //// Set request
122
105
  if (requestData != {}) request.data = requestData;
@@ -124,30 +107,15 @@ async function httpRequest(action) {
124
107
 
125
108
  // responseData
126
109
  //// Define
127
- if (action.responseData && action.responseData[0] === "$") {
128
- responseData = process.env[action.responseData.substring(1)];
129
- try {
130
- responseData = JSON.parse(responseData);
131
- } catch {}
132
- } else {
133
- responseData = action.responseData || defaultPayload.responseData;
134
- }
135
- //// Load environment variables
136
- responseData = loadEnvsForObject(responseData);
110
+ responseData = loadEnvs(action.responseData) || defaultPayload.responseData;
111
+
137
112
  //// Validate
138
113
 
139
114
  // responseHeaders
140
115
  //// Define
141
- if (action.responseHeaders && action.responseHeaders[0] === "$") {
142
- responseHeaders = process.env[action.responseHeaders.substring(1)];
143
- try {
144
- responseHeaders = JSON.parse(responseHeaders);
145
- } catch {}
146
- } else {
147
- responseHeaders = action.responseHeaders || defaultPayload.responseHeaders;
148
- }
149
- //// Load environment variables
150
- responseHeaders = loadEnvsForObject(responseHeaders);
116
+ responseHeaders =
117
+ loadEnvs(action.responseHeaders) || defaultPayload.responseHeaders;
118
+
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) => {
@@ -173,7 +169,7 @@ async function httpRequest(action) {
173
169
  // If request returned an error
174
170
  if (response.error) {
175
171
  status = "FAIL";
176
- description = `Error: ${JSON.stringify(response.error.response)}`;
172
+ description = `Error: ${JSON.stringify(response.error.message)}`;
177
173
  result = { status, description };
178
174
  return { result };
179
175
  }
@@ -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
- if (
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
- // Load environment variables
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(action.command);
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
- // Detect if using an environment variable and sub in value
478
- if (
479
- action.keys[0] === "$" &&
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
- if (
551
- action.text[0] === "$" &&
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";
@@ -775,7 +776,7 @@ function setFiles(config) {
775
776
  // Is a file
776
777
  isFile &&
777
778
  // Isn't present in files array already
778
- files.indexOf(s) < 0 &&
779
+ files.indexOf(content) < 0 &&
779
780
  // No extension filter or extension included in filter
780
781
  (config.testExtensions === "" ||
781
782
  config.testExtensions.includes(path.extname(content)))
@@ -1014,23 +1015,60 @@ async function log(config, level, message) {
1014
1015
  }
1015
1016
  }
1016
1017
 
1017
- function loadEnvsForObject(object) {
1018
- Object.keys(object).forEach((key) => {
1018
+ function loadEnvs(stringOrObject) {
1019
+ if (!stringOrObject) return stringOrObject;
1020
+ // Try to convert string to object
1021
+ try {
1019
1022
  if (
1020
- object[key][0] === "$" &&
1021
- process.env[object[key].substring(1)] != undefined
1023
+ typeof stringOrObject === "string" &&
1024
+ typeof JSON.parse(stringOrObject) === "object"
1022
1025
  ) {
1023
- object[key] = process.env[object[key].substring(1)];
1026
+ stringOrObject = JSON.parse(stringOrObject);
1024
1027
  }
1025
- try {
1026
- if (typeof JSON.parse(object[key]) === "object") {
1027
- object[key] = JSON.parse(object[key]);
1028
- }
1029
- } catch {}
1030
- if (typeof object[key] === "object") {
1031
- object[key] = loadEnvsForObject(object[key]);
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
 
Binary file
Binary file