kryptheon 0.1.11 → 0.1.12

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.
@@ -170,6 +170,94 @@ function firstMeaningfulLine(message) {
170
170
  return line ? line.replace(/^(TimeoutError|Error):\s*/, '').trim() : 'The test failed.';
171
171
  }
172
172
 
173
+ // ---------------------------------------------------------------------------
174
+ // Errors that belong to no test.
175
+ //
176
+ // A spec file with a syntax error, or one importing something that is not
177
+ // there, never becomes a test at all - so nothing calls onTestEnd and the run
178
+ // ends with no results. Playwright reports it through onError. A reporter that
179
+ // does not implement onError prints a summary of nothing and exits 1, and the
180
+ // person is left with a silent failure and no idea which file is at fault.
181
+ // ---------------------------------------------------------------------------
182
+
183
+ // Playwright says this once the load errors have already been reported. On its
184
+ // own it means an empty tests folder; after a real error it is a consequence,
185
+ // not a cause, and repeating it just buries the thing that has to be fixed.
186
+ function isNoTestsFound(error) {
187
+ return /No tests found/i.test(String((error && error.message) || ''));
188
+ }
189
+
190
+ /**
191
+ * A load error, reduced to the file, the line and one sentence.
192
+ *
193
+ * The message carries a code frame in ANSI colour and repeats the absolute
194
+ * path inside its own first line. Neither helps here: the path is printed
195
+ * separately and relative, and the frame belongs in an editor.
196
+ */
197
+ function readLoadError(error, userDir) {
198
+ const raw = stripAnsi((error && error.message) || (error && error.value) || String(error || ""));
199
+ const location = (error && error.location) || {};
200
+
201
+ let file = location.file || null;
202
+ if (file) {
203
+ try {
204
+ file = path.relative(userDir || process.cwd(), file);
205
+ } catch (e) {
206
+ /* an absolute path is still better than nothing */
207
+ }
208
+ }
209
+
210
+ // First line only, with the absolute path taken back out of it.
211
+ let message = raw.split("\n").map(function (l) { return l.trim(); }).filter(Boolean)[0] || "The file could not be read.";
212
+ if (location.file) message = message.split(location.file + ": ").join("");
213
+ message = message.replace(/\s*\(\d+:\d+\)\s*$/, "");
214
+
215
+ return {
216
+ file: file,
217
+ line: typeof location.line === "number" ? location.line : null,
218
+ message: message,
219
+ };
220
+ }
221
+
222
+ /**
223
+ * What to print when nothing could be loaded.
224
+ *
225
+ * Said as "this file", not "the tests failed", because nothing was tested. The
226
+ * app may be perfectly fine; a file in the tests folder is not.
227
+ */
228
+ function loadErrorLines(errors, userDir) {
229
+ const real = (errors || []).filter(function (e) { return !isNoTestsFound(e); });
230
+ const lines = [];
231
+
232
+ if (!real.length) {
233
+ // Nothing ran and nothing explained why. Saying so is still better than a
234
+ // summary of zero, which reads as "everything is fine".
235
+ lines.push("Nothing ran.");
236
+ lines.push("");
237
+ lines.push("No recording produced a result, and the test runner gave no reason.");
238
+ lines.push("Check that the files in tests/ contain a test.");
239
+ lines.push("");
240
+ return lines;
241
+ }
242
+
243
+ const many = real.length > 1;
244
+ lines.push(many ? real.length + " of your recordings could not be read:" : "One of your recordings could not be read:");
245
+ lines.push("");
246
+ for (const error of real) {
247
+ const read = readLoadError(error, userDir);
248
+ const where = read.file ? read.file + (read.line ? " line " + read.line : "") : "(the file was not named)";
249
+ lines.push(" " + where);
250
+ lines.push(" " + read.message);
251
+ lines.push("");
252
+ }
253
+ lines.push(many
254
+ ? "Nothing else ran, because those files could not be loaded at all."
255
+ : "Nothing else ran, because that file could not be loaded at all.");
256
+ lines.push("Fix it and run this again.");
257
+ lines.push("");
258
+ return lines;
259
+ }
260
+
173
261
  // ---------------------------------------------------------------------------
174
262
  // Translation: Playwright error -> { reason, advice }
175
263
  // ---------------------------------------------------------------------------
@@ -575,10 +663,19 @@ class KryptheonReporter {
575
663
  this.previousRuns = [];
576
664
  this.records = [];
577
665
  this.startedAt = new Date();
666
+ // Errors that belong to no test: a spec file that would not load.
667
+ this.loadErrors = [];
578
668
  // Set by any test whose page never said what encoding it was in.
579
669
  this.sawGuessedEncoding = false;
580
670
  }
581
671
 
672
+ // Playwright calls this for anything that happened outside a test: a spec
673
+ // file that would not load, most often. Kept rather than printed here, so
674
+ // the reason lands beside the summary instead of ahead of the run.
675
+ onError(error) {
676
+ this.loadErrors.push(error);
677
+ }
678
+
582
679
  onBegin() {
583
680
  this.startedAt = new Date();
584
681
  // Read history before this run is appended, so "was working on" looks
@@ -807,10 +904,35 @@ class KryptheonReporter {
807
904
  // has ever had a baseline - says the app is at fault when nothing is.
808
905
  const counts = recordings.summarise(this.records);
809
906
 
810
- // The one line stands in for the whole report only when there is no report
811
- // to make. A recording that has never passed is not counted as broken, but
812
- // it did fail and its reasons were printed above, so the run still owes the
813
- // reader its closing count.
907
+ // A file that would not load is said first, and always - before the quiet
908
+ // shortcut, and whatever else the run managed to do.
909
+ //
910
+ // In practice a file that will not load stops the whole run, so nothing
911
+ // else has anything to report and the block below returns. This is not
912
+ // written as though that were guaranteed: onError also carries errors that
913
+ // arrive outside any test, and one of those turning up after results would
914
+ // otherwise be dropped in silence - which is the bug this whole thing is
915
+ // here to fix.
916
+ const unreadable = this.loadErrors.some(function (e) { return !isNoTestsFound(e); });
917
+ if (unreadable) {
918
+ process.stdout.write(
919
+ String.fromCharCode(10) + loadErrorLines(this.loadErrors, USER_DIR).join(String.fromCharCode(10)),
920
+ );
921
+ }
922
+
923
+ // Nothing ran at all. A summary of zero reads as good news, and in quiet
924
+ // mode it would say "0 recordings still working" over the top of a file
925
+ // that does not compile, so neither is printed. If the reason has not
926
+ // already been given above, it is given now.
927
+ if (!this.records.length) {
928
+ if (!unreadable) {
929
+ process.stdout.write(
930
+ String.fromCharCode(10) + loadErrorLines(this.loadErrors, USER_DIR).join(String.fromCharCode(10)),
931
+ );
932
+ }
933
+ return;
934
+ }
935
+
814
936
  if (QUIET && !counts.broken && !counts.unproven) {
815
937
  const s = counts.working === 1 ? '' : 's';
816
938
  process.stdout.write('OK ' + counts.working + ' recording' + s + ' still working.\n');
@@ -838,3 +960,7 @@ module.exports = KryptheonReporter;
838
960
  // Shared with the fixture so baselines strip hosts exactly the same way.
839
961
  // Attached to the exported class, so `new (require(...))()` keeps working.
840
962
  module.exports.requestPath = requestPath;
963
+
964
+ // Exported for the checks: what a file that would not load turns into.
965
+ module.exports.readLoadError = readLoadError;
966
+ module.exports.loadErrorLines = loadErrorLines;
@@ -1,191 +1,191 @@
1
- // Which environment variables a recording needs, and whether they are there.
2
- //
3
- // Why this exists. When a password is typed during recording it is taken out
4
- // of the spec and replaced with `process.env.KRYPTHEON_PASSWORD || ''`. If
5
- // nobody ever creates the .env file, that expression quietly evaluates to an
6
- // empty string: the run submits a blank password, the login fails for the most
7
- // boring reason imaginable, and the report blames the app. Two hours can go
8
- // into that before anyone thinks to check the environment.
9
- //
10
- // So the names are read out of the spec before anything runs, and a recording
11
- // whose secret is missing is not run at all - a blank password is never sent.
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
-
16
- // The exact shape the scrubber writes. Anything else in the file that happens
17
- // to mention process.env is the author's own business and left alone.
18
- const SECRET_USE = /process\.env\.([A-Z][A-Z0-9_]*)\s*\|\|\s*(['"])\2/g;
19
-
20
- /** Every environment variable this recording falls back to an empty string for. */
21
- function requiredSecrets(source) {
22
- const out = [];
23
- const seen = Object.create(null);
24
- let match;
25
- SECRET_USE.lastIndex = 0;
26
- while ((match = SECRET_USE.exec(String(source || '')))) {
27
- const name = match[1];
28
- if (seen[name]) continue;
29
- seen[name] = true;
30
- out.push(name);
31
- }
32
- return out;
33
- }
34
-
35
- /**
36
- * Reads a .env file into a plain object. Never throws: a missing file simply
37
- * means nothing was defined there.
38
- *
39
- * Values are not expanded or unescaped beyond stripping one layer of quotes -
40
- * this only has to answer "is it set", and guessing at more would be a second
41
- * dialect of .env for people to trip over.
42
- */
43
- function readEnvFile(file) {
44
- const out = Object.create(null);
45
- let raw;
46
- try {
47
- raw = fs.readFileSync(file, 'utf8');
48
- } catch (e) {
49
- return out;
50
- }
51
- // A byte order mark would otherwise become part of the first name.
52
- if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
53
- for (const line of raw.split('\n')) {
54
- const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
55
- if (!match) continue;
56
- out[match[1]] = match[2].replace(/^(['"])([\s\S]*)\1$/, '$2');
57
- }
58
- return out;
59
- }
60
-
61
- /** A value that is present and not blank. An empty variable is not set. */
62
- function isSet(value) {
63
- return typeof value === 'string' && value.trim() !== '';
64
- }
65
-
66
- /**
67
- * Which of the names a recording needs are missing, looking at both the real
68
- * environment and the project's .env - the same two places the test itself
69
- * will look when it runs.
70
- */
71
- function missingSecrets(names, options) {
72
- const opts = options || {};
73
- const env = opts.env || process.env;
74
- const fromFile = opts.envFile ? readEnvFile(opts.envFile) : {};
75
- return (names || []).filter((name) => !isSet(env[name]) && !isSet(fromFile[name]));
76
- }
77
-
78
- /**
79
- * How to create the file without a byte order mark.
80
- *
81
- * PowerShell's own `Out-File -Encoding utf8` writes one, and so does Notepad,
82
- * which is why this spells out a command that does not rather than leaving it
83
- * to whatever the person reaches for first.
84
- */
85
- function howToWriteEnv(names, platform) {
86
- const line = names.map((name) => name + '=yourpassword').join('\\n');
87
- if ((platform || process.platform) === 'win32') {
88
- return [
89
- ' In PowerShell, this writes it without a byte order mark:',
90
- ' [IO.File]::WriteAllText("$PWD\\.env", "' + line + '`n")',
91
- '',
92
- ' Avoid Out-File and Notepad here: both add a mark that makes the first',
93
- ' line unreadable, and the password would silently come out blank.',
94
- ];
95
- }
96
- return [' For example:', " printf '" + line.replace(/\\n/g, "\\n") + "\\n' > .env"];
97
- }
98
-
99
- /** What to tell somebody who has not created the file yet. */
100
- function missingSecretLines(specName, names, options) {
101
- const opts = options || {};
102
- const many = names.length > 1;
103
- const lines = [
104
- ' Skipped ' + specName,
105
- ' This recording needs a password' + (many ? ' and other secrets' : '') + ', and there is none set.',
106
- '',
107
- ];
108
- if (opts.examplePath) {
109
- lines.push(' There is a ' + opts.examplePath + ' here already. Put the real value in it');
110
- lines.push(' and save it as .env, in the same folder.');
111
- } else {
112
- lines.push(' Create a .env file in this folder containing:');
113
- for (const name of names) lines.push(' ' + name + '=yourpassword');
114
- }
115
- lines.push('');
116
- for (const line of howToWriteEnv(names, opts.platform)) lines.push(line);
117
- lines.push('');
118
- lines.push(' Without it the recording would sign in with a blank password and');
119
- lines.push(' fail for a reason that has nothing to do with your app, so it was');
120
- lines.push(' not run. Keep .env out of version control.');
121
- return lines;
122
- }
123
-
124
- /**
125
- * Writes .env.example so nobody has to get the encoding right by hand.
126
- *
127
- * Plain UTF-8, no byte order mark. Never overwrites: the file may already hold
128
- * names from an earlier recording, and clobbering it would lose them.
129
- */
130
- function writeEnvExample(dir, names) {
131
- const file = path.join(dir, '.env.example');
132
- if (fs.existsSync(file)) return null;
133
- const body = (names || []).map((name) => name + '=yourpassword').join('\n') + '\n';
134
- try {
135
- fs.writeFileSync(file, body, { encoding: 'utf8' });
136
- return file;
137
- } catch (e) {
138
- return null; // a convenience, never a reason to fail a recording
139
- }
140
- }
141
-
142
- // Everything kryptheon leaves in a project that should not be committed: the
143
- // real password, and three files that are machine state rather than source.
144
- const GITIGNORE_ENTRIES = ['.env', 'kryptheon-baselines.json', 'kryptheon-history.jsonl', 'test-results/'];
145
-
146
- /**
147
- * Adds the missing entries to an existing .gitignore. Returns what it added.
148
- *
149
- * Only touches a file that is already there - creating one in a project that
150
- * has deliberately gone without is not this tool's decision to make.
151
- */
152
- function updateGitignore(dir, entries) {
153
- const file = path.join(dir, '.gitignore');
154
- let raw;
155
- try {
156
- raw = fs.readFileSync(file, { encoding: 'utf8' });
157
- } catch (e) {
158
- return [];
159
- }
160
- if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
161
- const present = new Set(
162
- raw
163
- .split('\n')
164
- .map((line) => line.trim().replace(/^\/+/, '').replace(/\/+$/, ''))
165
- .filter(Boolean),
166
- );
167
- const wanted = entries || GITIGNORE_ENTRIES;
168
- const missing = wanted.filter((entry) => !present.has(entry.replace(/\/+$/, '')));
169
- if (!missing.length) return [];
170
- const needsNewline = raw.length && !raw.endsWith('\n');
171
- const block = (needsNewline ? '\n' : '') + '\n# kryptheon\n' + missing.join('\n') + '\n';
172
- try {
173
- fs.appendFileSync(file, block, { encoding: 'utf8' });
174
- return missing;
175
- } catch (e) {
176
- return [];
177
- }
178
- }
179
-
180
- module.exports = {
181
- requiredSecrets: requiredSecrets,
182
- readEnvFile: readEnvFile,
183
- missingSecrets: missingSecrets,
184
- missingSecretLines: missingSecretLines,
185
- howToWriteEnv: howToWriteEnv,
186
- writeEnvExample: writeEnvExample,
187
- updateGitignore: updateGitignore,
188
- GITIGNORE_ENTRIES: GITIGNORE_ENTRIES,
189
- isSet: isSet,
190
- envFileFor: (dir) => path.join(dir, '.env'),
191
- };
1
+ // Which environment variables a recording needs, and whether they are there.
2
+ //
3
+ // Why this exists. When a password is typed during recording it is taken out
4
+ // of the spec and replaced with `process.env.KRYPTHEON_PASSWORD || ''`. If
5
+ // nobody ever creates the .env file, that expression quietly evaluates to an
6
+ // empty string: the run submits a blank password, the login fails for the most
7
+ // boring reason imaginable, and the report blames the app. Two hours can go
8
+ // into that before anyone thinks to check the environment.
9
+ //
10
+ // So the names are read out of the spec before anything runs, and a recording
11
+ // whose secret is missing is not run at all - a blank password is never sent.
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ // The exact shape the scrubber writes. Anything else in the file that happens
17
+ // to mention process.env is the author's own business and left alone.
18
+ const SECRET_USE = /process\.env\.([A-Z][A-Z0-9_]*)\s*\|\|\s*(['"])\2/g;
19
+
20
+ /** Every environment variable this recording falls back to an empty string for. */
21
+ function requiredSecrets(source) {
22
+ const out = [];
23
+ const seen = Object.create(null);
24
+ let match;
25
+ SECRET_USE.lastIndex = 0;
26
+ while ((match = SECRET_USE.exec(String(source || '')))) {
27
+ const name = match[1];
28
+ if (seen[name]) continue;
29
+ seen[name] = true;
30
+ out.push(name);
31
+ }
32
+ return out;
33
+ }
34
+
35
+ /**
36
+ * Reads a .env file into a plain object. Never throws: a missing file simply
37
+ * means nothing was defined there.
38
+ *
39
+ * Values are not expanded or unescaped beyond stripping one layer of quotes -
40
+ * this only has to answer "is it set", and guessing at more would be a second
41
+ * dialect of .env for people to trip over.
42
+ */
43
+ function readEnvFile(file) {
44
+ const out = Object.create(null);
45
+ let raw;
46
+ try {
47
+ raw = fs.readFileSync(file, 'utf8');
48
+ } catch (e) {
49
+ return out;
50
+ }
51
+ // A byte order mark would otherwise become part of the first name.
52
+ if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
53
+ for (const line of raw.split('\n')) {
54
+ const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*?)\s*$/);
55
+ if (!match) continue;
56
+ out[match[1]] = match[2].replace(/^(['"])([\s\S]*)\1$/, '$2');
57
+ }
58
+ return out;
59
+ }
60
+
61
+ /** A value that is present and not blank. An empty variable is not set. */
62
+ function isSet(value) {
63
+ return typeof value === 'string' && value.trim() !== '';
64
+ }
65
+
66
+ /**
67
+ * Which of the names a recording needs are missing, looking at both the real
68
+ * environment and the project's .env - the same two places the test itself
69
+ * will look when it runs.
70
+ */
71
+ function missingSecrets(names, options) {
72
+ const opts = options || {};
73
+ const env = opts.env || process.env;
74
+ const fromFile = opts.envFile ? readEnvFile(opts.envFile) : {};
75
+ return (names || []).filter((name) => !isSet(env[name]) && !isSet(fromFile[name]));
76
+ }
77
+
78
+ /**
79
+ * How to create the file without a byte order mark.
80
+ *
81
+ * PowerShell's own `Out-File -Encoding utf8` writes one, and so does Notepad,
82
+ * which is why this spells out a command that does not rather than leaving it
83
+ * to whatever the person reaches for first.
84
+ */
85
+ function howToWriteEnv(names, platform) {
86
+ const line = names.map((name) => name + '=yourpassword').join('\\n');
87
+ if ((platform || process.platform) === 'win32') {
88
+ return [
89
+ ' In PowerShell, this writes it without a byte order mark:',
90
+ ' [IO.File]::WriteAllText("$PWD\\.env", "' + line + '`n")',
91
+ '',
92
+ ' Avoid Out-File and Notepad here: both add a mark that makes the first',
93
+ ' line unreadable, and the password would silently come out blank.',
94
+ ];
95
+ }
96
+ return [' For example:', " printf '" + line.replace(/\\n/g, "\\n") + "\\n' > .env"];
97
+ }
98
+
99
+ /** What to tell somebody who has not created the file yet. */
100
+ function missingSecretLines(specName, names, options) {
101
+ const opts = options || {};
102
+ const many = names.length > 1;
103
+ const lines = [
104
+ ' Skipped ' + specName,
105
+ ' This recording needs a password' + (many ? ' and other secrets' : '') + ', and there is none set.',
106
+ '',
107
+ ];
108
+ if (opts.examplePath) {
109
+ lines.push(' There is a ' + opts.examplePath + ' here already. Put the real value in it');
110
+ lines.push(' and save it as .env, in the same folder.');
111
+ } else {
112
+ lines.push(' Create a .env file in this folder containing:');
113
+ for (const name of names) lines.push(' ' + name + '=yourpassword');
114
+ }
115
+ lines.push('');
116
+ for (const line of howToWriteEnv(names, opts.platform)) lines.push(line);
117
+ lines.push('');
118
+ lines.push(' Without it the recording would sign in with a blank password and');
119
+ lines.push(' fail for a reason that has nothing to do with your app, so it was');
120
+ lines.push(' not run. Keep .env out of version control.');
121
+ return lines;
122
+ }
123
+
124
+ /**
125
+ * Writes .env.example so nobody has to get the encoding right by hand.
126
+ *
127
+ * Plain UTF-8, no byte order mark. Never overwrites: the file may already hold
128
+ * names from an earlier recording, and clobbering it would lose them.
129
+ */
130
+ function writeEnvExample(dir, names) {
131
+ const file = path.join(dir, '.env.example');
132
+ if (fs.existsSync(file)) return null;
133
+ const body = (names || []).map((name) => name + '=yourpassword').join('\n') + '\n';
134
+ try {
135
+ fs.writeFileSync(file, body, { encoding: 'utf8' });
136
+ return file;
137
+ } catch (e) {
138
+ return null; // a convenience, never a reason to fail a recording
139
+ }
140
+ }
141
+
142
+ // Everything kryptheon leaves in a project that should not be committed: the
143
+ // real password, and three files that are machine state rather than source.
144
+ const GITIGNORE_ENTRIES = ['.env', 'kryptheon-baselines.json', 'kryptheon-history.jsonl', 'test-results/'];
145
+
146
+ /**
147
+ * Adds the missing entries to an existing .gitignore. Returns what it added.
148
+ *
149
+ * Only touches a file that is already there - creating one in a project that
150
+ * has deliberately gone without is not this tool's decision to make.
151
+ */
152
+ function updateGitignore(dir, entries) {
153
+ const file = path.join(dir, '.gitignore');
154
+ let raw;
155
+ try {
156
+ raw = fs.readFileSync(file, { encoding: 'utf8' });
157
+ } catch (e) {
158
+ return [];
159
+ }
160
+ if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
161
+ const present = new Set(
162
+ raw
163
+ .split('\n')
164
+ .map((line) => line.trim().replace(/^\/+/, '').replace(/\/+$/, ''))
165
+ .filter(Boolean),
166
+ );
167
+ const wanted = entries || GITIGNORE_ENTRIES;
168
+ const missing = wanted.filter((entry) => !present.has(entry.replace(/\/+$/, '')));
169
+ if (!missing.length) return [];
170
+ const needsNewline = raw.length && !raw.endsWith('\n');
171
+ const block = (needsNewline ? '\n' : '') + '\n# kryptheon\n' + missing.join('\n') + '\n';
172
+ try {
173
+ fs.appendFileSync(file, block, { encoding: 'utf8' });
174
+ return missing;
175
+ } catch (e) {
176
+ return [];
177
+ }
178
+ }
179
+
180
+ module.exports = {
181
+ requiredSecrets: requiredSecrets,
182
+ readEnvFile: readEnvFile,
183
+ missingSecrets: missingSecrets,
184
+ missingSecretLines: missingSecretLines,
185
+ howToWriteEnv: howToWriteEnv,
186
+ writeEnvExample: writeEnvExample,
187
+ updateGitignore: updateGitignore,
188
+ GITIGNORE_ENTRIES: GITIGNORE_ENTRIES,
189
+ isSet: isSet,
190
+ envFileFor: (dir) => path.join(dir, '.env'),
191
+ };
@@ -2,17 +2,17 @@
2
2
  // showing. Not a screenshot and not a pixel comparison - just the words a
3
3
  // person would use to say what is on screen.
4
4
  //
5
- // Why it exists. Automatic baselines currently remember the final address and
6
- // the page title. In a single-page app with a catch-all route those both stay
7
- // the same when a flow breaks: a login that no longer logs anyone in still
8
- // lands on the same URL with the same title, so the baseline sees no
9
- // difference and the run is reported as fine. The signature is the thing that
10
- // would have noticed - the headings, buttons, links and fields on the page
11
- // change even when the address does not.
5
+ // Why it exists. A baseline used to remember only the final address and the
6
+ // page title. In a single-page app with a catch-all route those both stay the
7
+ // same when a flow breaks: a login that no longer logs anyone in still lands on
8
+ // the same URL under the same title, so the baseline saw no difference and the
9
+ // run was reported as fine. The signature is the thing that notices - the
10
+ // headings, buttons, links and fields on a page change even when the address
11
+ // does not.
12
12
  //
13
- // STEP ONE ONLY. This module captures and prints. Nothing here is written to a
14
- // baseline, compared against anything, or allowed to affect whether a test
15
- // passes. Collection does not even run unless KRYPTHEON_DEBUG_SIGNATURE=1.
13
+ // A signature is part of the baseline: saved on a pass, compared on the next
14
+ // run, and a difference fails the test. KRYPTHEON_DEBUG_SIGNATURE=1 additionally
15
+ // prints what was captured, which is only ever a way to see the reasoning.
16
16
 
17
17
  const fs = require('fs');
18
18
 
@@ -495,7 +495,7 @@ function readRecorded(file) {
495
495
  }
496
496
 
497
497
  /**
498
- * Prints the signature. Step one does nothing else with it.
498
+ * Prints a signature, for when someone wants to see what was read.
499
499
  *
500
500
  * Goes to stderr so the reporter's own output, which is stdout, stays exactly
501
501
  * as it was - a person or a script reading the report sees no difference.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kryptheon",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Record what you do in your app and get plain-language reports when it breaks.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -38,21 +38,24 @@
38
38
  "@playwright/test": "^1.62.1"
39
39
  },
40
40
  "scripts": {
41
- "check": "node kryptheon-reporter.check.js && node kryptheon-signature.check.js && node kryptheon-signature.stream.check.js && node kryptheon-signature.baseline.check.js && node kryptheon-signature.settle.check.js && node kryptheon-signature.number.check.js && node kryptheon-step3.check.js && node kryptheon-watchdog.check.js && node kryptheon-record.check.js && node kryptheon-selectors.check.js && node kryptheon-recordings.check.js && node kryptheon-cleanup.check.js && node kryptheon-orphan.check.js && node kryptheon-project.check.js && node kryptheon-package.check.js",
41
+ "check": "node kryptheon-reporter.check.js && node kryptheon-signature.check.js && node kryptheon-signature.stream.check.js && node kryptheon-signature.baseline.check.js && node kryptheon-signature.settle.check.js && node kryptheon-signature.number.check.js && node kryptheon-env.check.js && node kryptheon-watchdog.check.js && node kryptheon-record.check.js && node kryptheon-selectors.check.js && node kryptheon-recordings.check.js && node kryptheon-cleanup.check.js && node kryptheon-orphan.check.js && node kryptheon-project.check.js && node kryptheon-load.check.js && node kryptheon-package.check.js",
42
+ "check:reporter": "node kryptheon-reporter.check.js",
43
+ "check:signature": "node kryptheon-signature.check.js",
42
44
  "check:stream": "node kryptheon-signature.stream.check.js",
43
45
  "check:baseline": "node kryptheon-signature.baseline.check.js",
44
46
  "check:settle": "node kryptheon-signature.settle.check.js",
45
- "check:step3": "node kryptheon-step3.check.js",
47
+ "check:number": "node kryptheon-signature.number.check.js",
48
+ "check:env": "node kryptheon-env.check.js",
46
49
  "check:watchdog": "node kryptheon-watchdog.check.js",
47
50
  "check:record": "node kryptheon-record.check.js",
48
- "check:package": "node kryptheon-package.check.js",
49
- "prepublishOnly": "node kryptheon-package.check.js",
50
51
  "check:selectors": "node kryptheon-selectors.check.js",
51
- "check:number": "node kryptheon-signature.number.check.js",
52
52
  "check:recordings": "node kryptheon-recordings.check.js",
53
53
  "check:cleanup": "node kryptheon-cleanup.check.js",
54
54
  "check:orphan": "node kryptheon-orphan.check.js",
55
- "check:project": "node kryptheon-project.check.js"
55
+ "check:project": "node kryptheon-project.check.js",
56
+ "check:load": "node kryptheon-load.check.js",
57
+ "check:package": "node kryptheon-package.check.js",
58
+ "prepublishOnly": "node kryptheon-package.check.js"
56
59
  },
57
60
  "keywords": [
58
61
  "playwright",