doc-detective 1.3.1 → 2.9.0-dev.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.
Files changed (44) hide show
  1. package/.github/FUNDING.yml +14 -0
  2. package/.github/dependabot.yml +11 -0
  3. package/.github/workflows/docker-image.yml +19 -0
  4. package/.github/workflows/electron-publish.yml +17 -0
  5. package/.github/workflows/npm-test.yaml +50 -0
  6. package/LICENSE +20 -20
  7. package/README.md +109 -42
  8. package/dev/dev.spec.json +62 -0
  9. package/dev/index.js +6 -0
  10. package/icon.png +0 -0
  11. package/index.js +1 -129
  12. package/package.json +43 -44
  13. package/samples/config.json +89 -0
  14. package/samples/doc-content-inline-tests.md +28 -0
  15. package/samples/doc-content.md +9 -0
  16. package/samples/reference.png +0 -0
  17. package/samples/screenshot.png +0 -0
  18. package/samples/tests.spec.json +70 -0
  19. package/samples/variables.env +5 -0
  20. package/src/index.js +44 -0
  21. package/src/utils.js +157 -1084
  22. package/test/artifacts/config.json +46 -0
  23. package/test/artifacts/doc-content.md +18 -0
  24. package/test/artifacts/test.spec.json +71 -0
  25. package/test/runCoverage.test.js +18 -0
  26. package/test/runTests.test.js +22 -0
  27. package/test/test-config.json +10 -0
  28. package/test/test-results.json +125 -0
  29. package/test/utils.test.js +204 -0
  30. package/.github/workflows/npm-publish.yml +0 -33
  31. package/config.json +0 -215
  32. package/src/analysis.js +0 -278
  33. package/src/analytics.js +0 -499
  34. package/src/coverage.js +0 -54
  35. package/src/install-mouse-helper.js +0 -72
  36. package/src/sanitize.js +0 -23
  37. package/src/suggest.js +0 -751
  38. package/src/tests/goTo.js +0 -39
  39. package/src/tests/httpRequest.js +0 -373
  40. package/src/tests/moveMouse.js +0 -91
  41. package/src/tests/record.js +0 -251
  42. package/src/tests/screenshot.js +0 -145
  43. package/src/tests/scroll.js +0 -32
  44. package/src/tests.js +0 -604
@@ -1,251 +0,0 @@
1
- const fs = require("fs");
2
- const { PuppeteerScreenRecorder } = require("puppeteer-screen-recorder");
3
- const { log } = require("../utils");
4
- const uuid = require("uuid");
5
- const path = require("path");
6
- const { exec } = require("child_process");
7
-
8
- exports.startRecording = startRecording;
9
- exports.stopRecording = stopRecording;
10
-
11
- async function startRecording(action, page, config) {
12
- let status;
13
- let description;
14
- let result;
15
- const formats = [".mp4", ".webm", ".gif"];
16
- const defaultPayload = {
17
- overwrite: false,
18
- mediaDirectory: config.mediaDirectory,
19
- filename: `${uuid.v4()}.mp4`,
20
- fps: 30,
21
- height: config.browserOptions.height,
22
- width: config.browserOptions.width,
23
- };
24
-
25
- // Set overwrite
26
- let overwrite = action.overwrite || defaultPayload.overwrite;
27
- switch (overwrite) {
28
- case true:
29
- case "true":
30
- overwrite = true;
31
- break;
32
- case false:
33
- case "false":
34
- overwrite = false;
35
- break;
36
- default:
37
- overwrite = defaultPayload.overwrite;
38
- log(
39
- config,
40
- "warning",
41
- `Invalid 'overwrite' value. Reverting to default: ${overwrite}`
42
- );
43
- }
44
-
45
- // Set mediaDirectory
46
- let mediaDirectory = action.mediaDirectory || defaultPayload.mediaDirectory;
47
- mediaDirectory = path.resolve(mediaDirectory);
48
- if (!fs.existsSync(mediaDirectory)) {
49
- mediaDirectory = defaultPayload.mediaDirectory;
50
- log(
51
- config,
52
- "warning",
53
- `Invalid media directory. Reverting to default: ${mediaDirectory}`
54
- );
55
- }
56
-
57
- // Set filename
58
- let filename = action.filename || defaultPayload.filename;
59
- let targetExtension = path.extname(action.filename);
60
- if (formats.indexOf(targetExtension) === -1) {
61
- filename = defaultPayload.filename;
62
- log(
63
- config,
64
- "warning",
65
- `Invalid filename. Reverting to default: ${filename}`
66
- );
67
- }
68
- if (targetExtension === ".gif") {
69
- tempExtension = ".mp4";
70
- } else {
71
- tempExtension = targetExtension;
72
- }
73
-
74
- // Set filepath
75
- filepath = path.join(mediaDirectory, filename);
76
- tempFilepath = path.join(
77
- mediaDirectory,
78
- "temp_" + path.parse(filename).name + tempExtension
79
- );
80
-
81
- if (fs.existsSync(filepath) && !action.overwrite) {
82
- // PASS: Don't record/overwrite
83
- status = "PASS";
84
- description = `Skipping action. Output file already exists, and overwrite set to 'false'.`;
85
- result = { status, description };
86
- return { result };
87
- }
88
-
89
- // Set FPS
90
- targetFps = action.fps || action.gifFps || defaultPayload.fps;
91
- try {
92
- targetFps = Number(targetFps);
93
- if (targetFps >= 30) {
94
- fps = targetFps;
95
- } else {
96
- fps = 30;
97
- }
98
- } catch {
99
- targetFps = defaultPayload.fps;
100
- log(config, "warning", `Invalid FPS. Reverting to default: ${targetFps}`);
101
- }
102
-
103
- // Set height
104
- height = action.height || defaultPayload.height;
105
- try {
106
- height = Number(height);
107
- } catch {
108
- height = defaultPayload;
109
- log(config, "warning", `Invalid height. Reverting to default: ${height}`);
110
- }
111
-
112
- // Set width
113
- width = action.width || action.gifWidth || defaultPayload.width;
114
- try {
115
- width = Number(width);
116
- } catch {
117
- width = defaultPayload;
118
- log(config, "warning", `Invalid width. Reverting to default: ${width}`);
119
- }
120
-
121
- try {
122
- const recorder = new PuppeteerScreenRecorder(page, { fps });
123
- await recorder.start(tempFilepath);
124
- // PASS
125
- status = "PASS";
126
- description = `Started recording: ${tempFilepath}`;
127
- result = { status, description, video: tempFilepath };
128
- videoDetails = {
129
- recorder,
130
- targetExtension,
131
- filepath,
132
- tempFilepath,
133
- fps,
134
- targetFps,
135
- height,
136
- width,
137
- };
138
- return { result, videoDetails };
139
- } catch {
140
- // FAIL: Couldn't capture screenshot
141
- status = "FAIL";
142
- description = `Couldn't start recording.`;
143
- result = { status, description };
144
- return { result };
145
- }
146
- }
147
-
148
- async function stopRecording(videoDetails, config) {
149
- let status;
150
- let description;
151
- let result;
152
-
153
- if (typeof videoDetails.recorder === "undefined") {
154
- status = "PASS";
155
- description = `Skipping action. No action-defined recording in progress.`;
156
- result = { status, description };
157
- return { result };
158
- }
159
-
160
- recorder = videoDetails.recorder;
161
- targetExtension = videoDetails.targetExtension;
162
- height = videoDetails.height;
163
- width = videoDetails.width;
164
- filepath = videoDetails.filepath;
165
- tempFilepath = videoDetails.tempFilepath;
166
- fps = videoDetails.fps;
167
- targetFps = videoDetails.targetFps;
168
-
169
- try {
170
- await recorder.stop();
171
- if (
172
- targetExtension === ".gif" ||
173
- height != config.browserOptions.height ||
174
- width != config.browserOptions.width ||
175
- targetFps != fps
176
- ) {
177
- let output = await convertVideo(config, videoDetails);
178
- filepath = output;
179
- } else {
180
- fs.renameSync(tempFilepath, filepath);
181
- log(config, "debug", `Removed intermediate file: ${tempFilepath}`);
182
- }
183
- // PASS
184
- status = "PASS";
185
- description = `Stopped recording: ${filepath}`;
186
- result = { status, description };
187
- return { result };
188
- } catch {
189
- // FAIL: Couldn't capture screenshot
190
- status = "FAIL";
191
- description = `Couldn't stop recording.`;
192
- result = { status, description };
193
- return { result };
194
- }
195
- }
196
-
197
- async function convertVideo(config, videoDetails) {
198
- const ffmpegPath = require("@ffmpeg-installer/ffmpeg").path;
199
-
200
- input = videoDetails.tempFilepath;
201
- output = videoDetails.filepath;
202
- fps = videoDetails.fps;
203
- targetExtension = videoDetails.targetExtension;
204
- height = videoDetails.height;
205
- if (
206
- height === config.browserOptions.height &&
207
- width != config.browserOptions.height
208
- ) {
209
- height = -2;
210
- }
211
- width = videoDetails.width;
212
- if (
213
- width === config.browserOptions.width &&
214
- height != config.browserOptions.width
215
- ) {
216
- width = -2;
217
- }
218
-
219
- if (!fs.existsSync(input)) return { error: "Invalid input." };
220
-
221
- switch (targetExtension) {
222
- case ".mp4":
223
- case ".webm":
224
- vf = `scale=${width}:${height}`;
225
- break;
226
- case ".gif":
227
- vf = `fps=${fps},scale=${width}:${height}:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse`;
228
- }
229
-
230
- let command = `${ffmpegPath} -nostats -loglevel 0 -y -i "${input}" -vf "${vf}" -loop 0 "${output}"`;
231
- exec(command, (error, stdout, stderr) => {
232
- if (error) {
233
- log(config, "debug", error.message);
234
- return { error: error.message };
235
- }
236
- if (stderr) {
237
- log(config, "debug", stderr);
238
- return { stderr };
239
- }
240
- log(config, "debug", stdout);
241
- fs.unlink(input, function (err) {
242
- if (err) {
243
- log(config, "warning", `Couldn't delete intermediate file: ${input}`);
244
- } else {
245
- log(config, "debug", `Deleted intermediate file: ${input}`);
246
- }
247
- });
248
- return { stdout };
249
- });
250
- return output;
251
- }
@@ -1,145 +0,0 @@
1
- const fs = require("fs");
2
- const PNG = require("pngjs").PNG;
3
- const pixelmatch = require("pixelmatch");
4
- const path = require("path");
5
- const { log } = require("../utils");
6
-
7
- exports.screenshot = screenshot;
8
-
9
- async function screenshot(action, page, config) {
10
- let status;
11
- let description;
12
- let result;
13
- let defaultPayload = {
14
- overwrite: false,
15
- mediaDirectory: "samples",
16
- filename: "results.png",
17
- matchPrevious: true,
18
- matchThreshold: 0.1,
19
- };
20
-
21
- // Set directory
22
- filePath = action.mediaDirectory || config.mediaDirectory;
23
-
24
- if (!fs.existsSync(filePath)) {
25
- // FAIL: Invalid path
26
- status = "FAIL";
27
- description = `Invalid directory path.`;
28
- result = { status, description };
29
- return { result };
30
- }
31
-
32
- // if (fs.existsSync(filePath) && !action.matchPrevious && !action.overwrite) {
33
- // // PASS
34
- // status = "PASS";
35
- // description = `Skipping action. Output file already exists, and 'overwrite' set to 'false', and 'matchPrevious' is set to 'false'.`;
36
- // result = { status, description };
37
- // return { result };
38
- // }
39
-
40
- if (action.matchPrevious && action.filename) {
41
- let testPath = path.join(filePath, action.filename);
42
- const fileExists = fs.existsSync(testPath);
43
- if (fileExists) {
44
- filename = "temp_" + action.filename;
45
- previousFilename = action.filename;
46
- previousFilePath = path.join(filePath, previousFilename);
47
- // Set threshold
48
- if (!(action.matchThreshold >= 0 && action.matchThreshold <= 1)) {
49
- action.matchThreshold = 0.1;
50
- }
51
- } else {
52
- action.matchPrevious = false;
53
- log(
54
- config,
55
- "warning",
56
- "Specified filename doesn't exist. Capturing screenshot. Not matching."
57
- );
58
- filename = action.filename;
59
- }
60
- } else if (action.matchPrevious && !action.filename) {
61
- action.matchPrevious = false;
62
- log(config, "warning", "No filename specified. Not matching.");
63
- filename = "temp_" + action.filename;
64
- } else if (!action.matchPrevious && action.filename) {
65
- filename = action.filename;
66
- } else {
67
- filename = "temp_" + action.filename;
68
- }
69
- filePath = path.join(filePath, filename);
70
-
71
- try {
72
- await page.screenshot({ path: filePath });
73
- if (!action.matchPrevious) {
74
- // PASS
75
- status = "PASS";
76
- description = `Captured screenshot.`;
77
- result = { status, description, image: filePath };
78
- return { result };
79
- }
80
- } catch {
81
- // FAIL: Couldn't capture screenshot
82
- status = "FAIL";
83
- description = `Couldn't capture screenshot.`;
84
- result = { status, description };
85
- return { result };
86
- }
87
- if (action.matchPrevious) {
88
- const expected = PNG.sync.read(fs.readFileSync(previousFilePath));
89
- const actual = PNG.sync.read(fs.readFileSync(filePath));
90
- try {
91
- const numDiffPixels = pixelmatch(
92
- expected.data,
93
- actual.data,
94
- null,
95
- expected.width,
96
- expected.height,
97
- {
98
- threshold: action.matchThreshold,
99
- }
100
- );
101
- fs.unlink(filePath, function (err) {
102
- if (err) {
103
- log(
104
- config,
105
- "warning",
106
- `Couldn't delete intermediate file: ${filePath}`
107
- );
108
- } else {
109
- log(config, "debug", `Deleted intermediate file: ${filePath}`);
110
- }
111
- });
112
- if (numDiffPixels) {
113
- // FAIL: Couldn't capture screenshot
114
- const diffPercentage =
115
- numDiffPixels / (expected.width * expected.height);
116
- status = "FAIL";
117
- description = `Screenshot comparison had larger diff (${diffPercentage}) than threshold (${action.matchThreshold}).`;
118
- result = { status, description };
119
- return { result };
120
- } else {
121
- // PASS
122
- status = "PASS";
123
- description = `Screenshot matches previously captured image.`;
124
- result = { status, description, image: previousFilePath };
125
- return { result };
126
- }
127
- } catch {
128
- fs.unlink(filePath, function (err) {
129
- if (err) {
130
- log(
131
- config,
132
- "warning",
133
- `Couldn't delete intermediate file: ${filePath}`
134
- );
135
- } else {
136
- log(config, "debug", `Deleted intermediate file: ${filePath}`);
137
- }
138
- });
139
- status = "FAIL";
140
- description = `Image sizes don't match.`;
141
- result = { status, description };
142
- return { result };
143
- }
144
- }
145
- }
@@ -1,32 +0,0 @@
1
- exports.scroll = scroll;
2
-
3
- async function scroll(action, page, config) {
4
- let status;
5
- let description;
6
- let result;
7
-
8
- if (
9
- Object.keys(config.videoDetails).length === 0 &&
10
- Object.keys(config.debugRecording).length === 0
11
- ) {
12
- status = "PASS";
13
- description = "Skipping action. No recordings are in progress.";
14
- result = { status, description };
15
- return { result };
16
- }
17
-
18
- try {
19
- await page.mouse.wheel({ deltaX: action.x, deltaY: action.y });
20
- // PASS
21
- status = "PASS";
22
- description = `Scroll complete.`;
23
- result = { status, description };
24
- return { result };
25
- } catch {
26
- // FAIL
27
- status = "PASS";
28
- description = `Couldn't scroll.`;
29
- result = { status, description };
30
- return { result };
31
- }
32
- }