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.
- package/.github/FUNDING.yml +14 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/docker-image.yml +19 -0
- package/.github/workflows/electron-publish.yml +17 -0
- package/.github/workflows/npm-test.yaml +50 -0
- package/LICENSE +20 -20
- package/README.md +109 -42
- package/dev/dev.spec.json +62 -0
- package/dev/index.js +6 -0
- package/icon.png +0 -0
- package/index.js +1 -129
- package/package.json +43 -44
- package/samples/config.json +89 -0
- package/samples/doc-content-inline-tests.md +28 -0
- package/samples/doc-content.md +9 -0
- package/samples/reference.png +0 -0
- package/samples/screenshot.png +0 -0
- package/samples/tests.spec.json +70 -0
- package/samples/variables.env +5 -0
- package/src/index.js +44 -0
- package/src/utils.js +157 -1084
- package/test/artifacts/config.json +46 -0
- package/test/artifacts/doc-content.md +18 -0
- package/test/artifacts/test.spec.json +71 -0
- package/test/runCoverage.test.js +18 -0
- package/test/runTests.test.js +22 -0
- package/test/test-config.json +10 -0
- package/test/test-results.json +125 -0
- package/test/utils.test.js +204 -0
- package/.github/workflows/npm-publish.yml +0 -33
- package/config.json +0 -215
- package/src/analysis.js +0 -278
- package/src/analytics.js +0 -499
- package/src/coverage.js +0 -54
- package/src/install-mouse-helper.js +0 -72
- package/src/sanitize.js +0 -23
- package/src/suggest.js +0 -751
- package/src/tests/goTo.js +0 -39
- package/src/tests/httpRequest.js +0 -373
- package/src/tests/moveMouse.js +0 -91
- package/src/tests/record.js +0 -251
- package/src/tests/screenshot.js +0 -145
- package/src/tests/scroll.js +0 -32
- package/src/tests.js +0 -604
package/src/tests/record.js
DELETED
|
@@ -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
|
-
}
|
package/src/tests/screenshot.js
DELETED
|
@@ -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
|
-
}
|
package/src/tests/scroll.js
DELETED
|
@@ -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
|
-
}
|