@browsercore/testing 0.1.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/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/bench/bench.d.ts +21 -0
- package/dist/bench/bench.d.ts.map +1 -0
- package/dist/bench/bench.js +19 -0
- package/dist/bench/bench.js.map +1 -0
- package/dist/captures/manifest.d.ts +18 -0
- package/dist/captures/manifest.d.ts.map +1 -0
- package/dist/captures/manifest.js +62 -0
- package/dist/captures/manifest.js.map +1 -0
- package/dist/categories/capture.d.ts +8 -0
- package/dist/categories/capture.d.ts.map +1 -0
- package/dist/categories/capture.js +31 -0
- package/dist/categories/capture.js.map +1 -0
- package/dist/categories/compression.d.ts +8 -0
- package/dist/categories/compression.d.ts.map +1 -0
- package/dist/categories/compression.js +15 -0
- package/dist/categories/compression.js.map +1 -0
- package/dist/categories/cookies.d.ts +8 -0
- package/dist/categories/cookies.d.ts.map +1 -0
- package/dist/categories/cookies.js +20 -0
- package/dist/categories/cookies.js.map +1 -0
- package/dist/categories/errors.d.ts +11 -0
- package/dist/categories/errors.d.ts.map +1 -0
- package/dist/categories/errors.js +41 -0
- package/dist/categories/errors.js.map +1 -0
- package/dist/categories/http.d.ts +9 -0
- package/dist/categories/http.d.ts.map +1 -0
- package/dist/categories/http.js +46 -0
- package/dist/categories/http.js.map +1 -0
- package/dist/categories/index.d.ts +17 -0
- package/dist/categories/index.d.ts.map +1 -0
- package/dist/categories/index.js +17 -0
- package/dist/categories/index.js.map +1 -0
- package/dist/categories/redirects.d.ts +8 -0
- package/dist/categories/redirects.d.ts.map +1 -0
- package/dist/categories/redirects.js +21 -0
- package/dist/categories/redirects.js.map +1 -0
- package/dist/categories/regression.d.ts +8 -0
- package/dist/categories/regression.d.ts.map +1 -0
- package/dist/categories/regression.js +28 -0
- package/dist/categories/regression.js.map +1 -0
- package/dist/categories/session.d.ts +8 -0
- package/dist/categories/session.d.ts.map +1 -0
- package/dist/categories/session.js +23 -0
- package/dist/categories/session.js.map +1 -0
- package/dist/categories/tls.d.ts +10 -0
- package/dist/categories/tls.d.ts.map +1 -0
- package/dist/categories/tls.js +48 -0
- package/dist/categories/tls.js.map +1 -0
- package/dist/categories/transport.d.ts +8 -0
- package/dist/categories/transport.d.ts.map +1 -0
- package/dist/categories/transport.js +22 -0
- package/dist/categories/transport.js.map +1 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/errors.js.map +1 -0
- package/dist/fingerprint/index.d.ts +5 -0
- package/dist/fingerprint/index.d.ts.map +1 -0
- package/dist/fingerprint/index.js +4 -0
- package/dist/fingerprint/index.js.map +1 -0
- package/dist/fingerprint/ja3.d.ts +36 -0
- package/dist/fingerprint/ja3.d.ts.map +1 -0
- package/dist/fingerprint/ja3.js +141 -0
- package/dist/fingerprint/ja3.js.map +1 -0
- package/dist/fingerprint/ja4.d.ts +109 -0
- package/dist/fingerprint/ja4.d.ts.map +1 -0
- package/dist/fingerprint/ja4.js +325 -0
- package/dist/fingerprint/ja4.js.map +1 -0
- package/dist/golden/golden.d.ts +45 -0
- package/dist/golden/golden.d.ts.map +1 -0
- package/dist/golden/golden.js +216 -0
- package/dist/golden/golden.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/reference/node-reference.d.ts +83 -0
- package/dist/reference/node-reference.d.ts.map +1 -0
- package/dist/reference/node-reference.js +161 -0
- package/dist/reference/node-reference.js.map +1 -0
- package/dist/reference/reference.d.ts +143 -0
- package/dist/reference/reference.d.ts.map +1 -0
- package/dist/reference/reference.js +323 -0
- package/dist/reference/reference.js.map +1 -0
- package/dist/rfc/rfcTests.d.ts +14 -0
- package/dist/rfc/rfcTests.d.ts.map +1 -0
- package/dist/rfc/rfcTests.js +22 -0
- package/dist/rfc/rfcTests.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +34 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +29 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +102 -0
- package/dist/utils.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable layered reference provider.
|
|
3
|
+
*
|
|
4
|
+
* The reference implementation is the source of truth for what a given browser
|
|
5
|
+
* profile SHOULD produce on the wire. Three backends are wrapped behind one
|
|
6
|
+
* facade:
|
|
7
|
+
*
|
|
8
|
+
* - `node-reference` (oracle) — Node's built-in crypto/dns/zlib/http
|
|
9
|
+
* implementations, used as the spec oracle for primitive layers where Node IS
|
|
10
|
+
* the reference (see `node-reference.ts`).
|
|
11
|
+
* - `curl-impersonate` (PRIMARY) — fast CI path; shells out to the
|
|
12
|
+
* curl-impersonate binary to capture real traffic for a profile.
|
|
13
|
+
* - `real-browser` (SECONDARY) — loads pre-recorded captures from the
|
|
14
|
+
* `captures/` directory, recorded from an actual browser.
|
|
15
|
+
*
|
|
16
|
+
* The {@link ReferenceProviderFacade} ties them together: capture resolves via
|
|
17
|
+
* primary then secondary; fingerprint is derived from the captured bytes; the
|
|
18
|
+
* node-reference oracle is exposed for primitive-layer comparisons.
|
|
19
|
+
*
|
|
20
|
+
* See docs/TEST-SUITE.md (Cat 3, 4, 14) for how references feed comparison.
|
|
21
|
+
*/
|
|
22
|
+
import { execFile } from "node:child_process";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { dirname, join } from "node:path";
|
|
25
|
+
import { promisify } from "node:util";
|
|
26
|
+
import { computeJa3 } from "../fingerprint/ja3.js";
|
|
27
|
+
import { computeJa4Fingerprint } from "../fingerprint/ja4.js";
|
|
28
|
+
import { loadCaptureMeta } from "../golden/golden.js";
|
|
29
|
+
import { TestingError } from "../errors.js";
|
|
30
|
+
const execFileAsync = promisify(execFile);
|
|
31
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const packageRoot = join(here, "..", "..");
|
|
33
|
+
const defaultCapturesDir = join(packageRoot, "captures");
|
|
34
|
+
/** Raised when a reference provider cannot capture or fingerprint a profile. */
|
|
35
|
+
export class ReferenceError extends TestingError {
|
|
36
|
+
constructor(message, options) {
|
|
37
|
+
super(message, options);
|
|
38
|
+
this.name = "ReferenceError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* PRIMARY provider — shells out to curl-impersonate.
|
|
43
|
+
*
|
|
44
|
+
* `capture()` invokes the curl-impersonate binary against `url`, impersonating
|
|
45
|
+
* `profile`, and returns the raw bytes it observed on the wire. The binary
|
|
46
|
+
* must be on PATH (or in `command`); if it is missing, the call throws
|
|
47
|
+
* {@link ReferenceError} so the facade can fall back to the secondary.
|
|
48
|
+
*/
|
|
49
|
+
export class CurlImpersonateProvider {
|
|
50
|
+
kind = { kind: "curl-impersonate" };
|
|
51
|
+
command;
|
|
52
|
+
extraArgs;
|
|
53
|
+
constructor(options) {
|
|
54
|
+
this.command = options?.command ?? "curl-impersonate";
|
|
55
|
+
this.extraArgs = options?.extraArgs ?? [];
|
|
56
|
+
}
|
|
57
|
+
availableProfiles() {
|
|
58
|
+
// curl-impersonate ships these browser impersonation targets.
|
|
59
|
+
return [
|
|
60
|
+
"chrome-140",
|
|
61
|
+
"chrome-139",
|
|
62
|
+
"firefox-135",
|
|
63
|
+
"firefox-128",
|
|
64
|
+
"safari-18",
|
|
65
|
+
"edge-140",
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
async capture(profile, url) {
|
|
69
|
+
const profileFlag = `--${String(profile)}`;
|
|
70
|
+
const args = [profileFlag, "--dump-traffic", ...this.extraArgs, url];
|
|
71
|
+
let stdout;
|
|
72
|
+
let stderr;
|
|
73
|
+
try {
|
|
74
|
+
const out = await execFileAsync(this.command, args, {
|
|
75
|
+
timeout: 30_000,
|
|
76
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
77
|
+
});
|
|
78
|
+
stdout = out.stdout;
|
|
79
|
+
stderr = out.stderr;
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
const cause = e instanceof Error ? e : new Error(String(e));
|
|
83
|
+
throw new ReferenceError(`curl-impersonate capture for ${String(profile)} failed: ${cause.message}`, { cause });
|
|
84
|
+
}
|
|
85
|
+
void stderr;
|
|
86
|
+
const bytes = parseDumpOutput(stdout);
|
|
87
|
+
return {
|
|
88
|
+
id: `${profile}/tls/client_hello`,
|
|
89
|
+
source: profileToSource(profile),
|
|
90
|
+
protocol: "tls",
|
|
91
|
+
bytes,
|
|
92
|
+
description: `curl-impersonate capture for ${String(profile)}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async fingerprint(profile) {
|
|
96
|
+
const capture = await this.capture(profile, "https://example.com");
|
|
97
|
+
return fingerprintFromTlsCapture(capture);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* SECONDARY provider — loads pre-recorded captures from the `captures/` dir.
|
|
102
|
+
*
|
|
103
|
+
* `capture()` resolves a stored capture for the profile; `fingerprint()`
|
|
104
|
+
* derives a {@link Fingerprint} from the captured bytes (only TLS
|
|
105
|
+
* ClientHellos are fingerprinted — other protocols return a stub).
|
|
106
|
+
*/
|
|
107
|
+
export class RealBrowserCaptureProvider {
|
|
108
|
+
kind = { kind: "real-browser" };
|
|
109
|
+
capturesDir;
|
|
110
|
+
constructor(options) {
|
|
111
|
+
this.capturesDir = options?.capturesDir ?? defaultCapturesDir;
|
|
112
|
+
}
|
|
113
|
+
availableProfiles() {
|
|
114
|
+
// Discovery is driven by the captures manifest (captures/manifest.ts).
|
|
115
|
+
return [
|
|
116
|
+
"chrome-140",
|
|
117
|
+
"firefox-128",
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
async capture(profile, _url) {
|
|
121
|
+
void _url;
|
|
122
|
+
const manifest = await import("../captures/manifest.js");
|
|
123
|
+
const entry = manifest.captures.find((c) => c.meta.profile === profile);
|
|
124
|
+
if (entry === undefined) {
|
|
125
|
+
throw new ReferenceError(`No pre-recorded capture for profile ${String(profile)}`);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
id: `${profile}/tls/client_hello`,
|
|
129
|
+
source: profileToSource(profile),
|
|
130
|
+
protocol: entry.meta.protocol,
|
|
131
|
+
bytes: entry.bytes,
|
|
132
|
+
description: entry.meta.description,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async fingerprint(profile) {
|
|
136
|
+
const capture = await this.capture(profile, "https://example.com");
|
|
137
|
+
if (capture.protocol !== "tls") {
|
|
138
|
+
throw new ReferenceError(`fingerprint() only supports TLS captures; got ${capture.protocol}`);
|
|
139
|
+
}
|
|
140
|
+
return fingerprintFromTlsCapture(capture);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Facade wrapping the node-reference oracle + primary (curl-impersonate) and
|
|
145
|
+
* secondary (real-browser) providers behind a single {@link ReferenceProvider}.
|
|
146
|
+
*
|
|
147
|
+
* `capture()` tries the primary first; if it fails (binary missing, network
|
|
148
|
+
* error, ...) it falls back to the secondary's pre-recorded captures. The
|
|
149
|
+
* node-reference oracle is exposed via {@link ReferenceProviderFacade.nodeOracle}
|
|
150
|
+
* for primitive-layer comparisons (crypto, dns, zlib, wire format) — those
|
|
151
|
+
* layers are NOT fingerprinted here; they live in `node-reference.ts`.
|
|
152
|
+
*/
|
|
153
|
+
export class ReferenceProviderFacade {
|
|
154
|
+
kind = { kind: "curl-impersonate" };
|
|
155
|
+
primary;
|
|
156
|
+
secondary;
|
|
157
|
+
constructor(options) {
|
|
158
|
+
this.primary = new CurlImpersonateProvider(options?.curl);
|
|
159
|
+
this.secondary = new RealBrowserCaptureProvider(options?.browser);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Node-reference oracle — the spec oracle for primitive layers where Node
|
|
163
|
+
* IS the reference (crypto, dns, zlib, wire format). NOT used for
|
|
164
|
+
* browser-fingerprint comparison; that is what the providers are for.
|
|
165
|
+
*/
|
|
166
|
+
get nodeOracle() {
|
|
167
|
+
return nodeOracle;
|
|
168
|
+
}
|
|
169
|
+
availableProfiles() {
|
|
170
|
+
// Union of profiles from both providers, de-duplicated.
|
|
171
|
+
const seen = new Set();
|
|
172
|
+
const out = [];
|
|
173
|
+
for (const p of [...this.primary.availableProfiles(), ...this.secondary.availableProfiles()]) {
|
|
174
|
+
const key = String(p);
|
|
175
|
+
if (seen.has(key))
|
|
176
|
+
continue;
|
|
177
|
+
seen.add(key);
|
|
178
|
+
out.push(p);
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
async capture(profile, url) {
|
|
183
|
+
try {
|
|
184
|
+
return await this.primary.capture(profile, url);
|
|
185
|
+
}
|
|
186
|
+
catch (primaryErr) {
|
|
187
|
+
const cause = primaryErr instanceof Error ? primaryErr : new Error(String(primaryErr));
|
|
188
|
+
// Fall back to the secondary provider's pre-recorded captures.
|
|
189
|
+
try {
|
|
190
|
+
return await this.secondary.capture(profile, url);
|
|
191
|
+
}
|
|
192
|
+
catch (secondaryErr) {
|
|
193
|
+
const secondaryCause = secondaryErr instanceof Error ? secondaryErr : new Error(String(secondaryErr));
|
|
194
|
+
throw new ReferenceError(`No reference available for ${String(profile)}: primary failed (${cause.message}), secondary failed (${secondaryCause.message})`, { cause: secondaryCause });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async fingerprint(profile) {
|
|
199
|
+
try {
|
|
200
|
+
return await this.primary.fingerprint(profile);
|
|
201
|
+
}
|
|
202
|
+
catch (primaryErr) {
|
|
203
|
+
const cause = primaryErr instanceof Error ? primaryErr : new Error(String(primaryErr));
|
|
204
|
+
try {
|
|
205
|
+
return await this.secondary.fingerprint(profile);
|
|
206
|
+
}
|
|
207
|
+
catch (secondaryErr) {
|
|
208
|
+
const secondaryCause = secondaryErr instanceof Error ? secondaryErr : new Error(String(secondaryErr));
|
|
209
|
+
throw new ReferenceError(`No fingerprint available for ${String(profile)}: primary failed (${cause.message}), secondary failed (${secondaryCause.message})`, { cause: secondaryCause });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Node-reference oracle re-export — the spec oracle for primitive layers.
|
|
216
|
+
*
|
|
217
|
+
* Imported lazily so the facade type references resolve without a cycle.
|
|
218
|
+
*/
|
|
219
|
+
import * as nodeOracle from "./node-reference.js";
|
|
220
|
+
/** Construct the {@link ReferenceProvider} matching {@link ReferenceProviderKind}. */
|
|
221
|
+
export function createReferenceProvider(kind, options) {
|
|
222
|
+
switch (kind.kind) {
|
|
223
|
+
case "curl-impersonate":
|
|
224
|
+
return new CurlImpersonateProvider(options);
|
|
225
|
+
case "real-browser":
|
|
226
|
+
return new RealBrowserCaptureProvider(options);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Construct the full {@link ReferenceProviderFacade} wrapping both providers
|
|
231
|
+
* plus the node-reference oracle. This is the recommended entry point for
|
|
232
|
+
* tests that want primary→secondary fallback.
|
|
233
|
+
*/
|
|
234
|
+
export function createReferenceFacade(options) {
|
|
235
|
+
return new ReferenceProviderFacade(options);
|
|
236
|
+
}
|
|
237
|
+
/** Map a {@link ProfileId} to its {@link CaptureSource} tag. */
|
|
238
|
+
function profileToSource(profile) {
|
|
239
|
+
const p = String(profile);
|
|
240
|
+
if (p.startsWith("firefox"))
|
|
241
|
+
return "firefox-135";
|
|
242
|
+
if (p.startsWith("safari"))
|
|
243
|
+
return "safari-18";
|
|
244
|
+
if (p.startsWith("edge"))
|
|
245
|
+
return "edge-140";
|
|
246
|
+
return "chrome-140";
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Parse curl-impersonate `--dump-traffic` output into raw bytes.
|
|
250
|
+
*
|
|
251
|
+
* The dump format is a hex dump with one line per record; we extract the hex
|
|
252
|
+
* payload between the markers. For now we assume the body is a contiguous hex
|
|
253
|
+
* block — adjust the parser if curl-impersonate's format differs.
|
|
254
|
+
*/
|
|
255
|
+
function parseDumpOutput(stdout) {
|
|
256
|
+
// Find the hex body — everything after the ">>> traffic <<<" marker.
|
|
257
|
+
const marker = ">>> traffic <<<";
|
|
258
|
+
const idx = stdout.indexOf(marker);
|
|
259
|
+
const body = idx === -1 ? stdout : stdout.slice(idx + marker.length);
|
|
260
|
+
const hex = body.replace(/[^0-9a-fA-F]/g, "");
|
|
261
|
+
if (hex.length === 0) {
|
|
262
|
+
throw new ReferenceError("curl-impersonate dump produced no hex bytes");
|
|
263
|
+
}
|
|
264
|
+
if (hex.length % 2 !== 0) {
|
|
265
|
+
throw new ReferenceError(`curl-impersonate dump produced odd-length hex (${hex.length})`);
|
|
266
|
+
}
|
|
267
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
268
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
269
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
270
|
+
}
|
|
271
|
+
return bytes;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Derive a {@link Fingerprint} from a TLS ClientHello capture.
|
|
275
|
+
*
|
|
276
|
+
* Computes JA3 + JA4 from the raw bytes and reads the per-extension metadata
|
|
277
|
+
* (supported groups, signature algorithms, ALPN) from the sidecar
|
|
278
|
+
* `.meta.json` when available.
|
|
279
|
+
*/
|
|
280
|
+
function fingerprintFromTlsCapture(capture) {
|
|
281
|
+
const ja3 = computeJa3(capture.bytes);
|
|
282
|
+
const ja4 = computeJa4Fingerprint(capture.bytes);
|
|
283
|
+
// Read the sidecar meta for richer fields (signature algorithms, ALPN,
|
|
284
|
+
// supported curves). Fall back to empty arrays if missing.
|
|
285
|
+
let alpn = [];
|
|
286
|
+
let signatureAlgorithms = [];
|
|
287
|
+
let ellipticCurves = [];
|
|
288
|
+
try {
|
|
289
|
+
const meta = loadCaptureMeta(capture.id);
|
|
290
|
+
if (meta.protocol === "tls") {
|
|
291
|
+
// CaptureMeta doesn't carry ALPN/sigAlgs/curves yet; this is a
|
|
292
|
+
// placeholder for when the sidecar schema is extended.
|
|
293
|
+
void meta;
|
|
294
|
+
void alpn;
|
|
295
|
+
void signatureAlgorithms;
|
|
296
|
+
void ellipticCurves;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// Sidecar missing or unparseable — leave richer fields empty.
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
ja3,
|
|
304
|
+
ja4: ja4.tag,
|
|
305
|
+
alpn,
|
|
306
|
+
cipherSuite: cipherSuiteName(ja4.tag),
|
|
307
|
+
protocolVersion: ja4.tag.slice(5, 7) || "unknown",
|
|
308
|
+
signatureAlgorithms,
|
|
309
|
+
ellipticCurves,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Extract a human-readable cipher-suite name from a JA4 tag.
|
|
314
|
+
*
|
|
315
|
+
* JA4 doesn't directly encode the negotiated cipher; this is a placeholder
|
|
316
|
+
* that returns the JA4_a segment for inspection. Real cipher-suite resolution
|
|
317
|
+
* requires parsing the ServerHello, which is out of scope for the capture.
|
|
318
|
+
*/
|
|
319
|
+
function cipherSuiteName(ja4Tag) {
|
|
320
|
+
const a = ja4Tag.split("_")[0] ?? "";
|
|
321
|
+
return a.length > 0 ? a : "unknown";
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=reference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference.js","sourceRoot":"","sources":["../../src/reference/reference.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC compliance test suites.
|
|
3
|
+
*
|
|
4
|
+
* Each function runs a focused compliance check and returns a {@link TestResult}.
|
|
5
|
+
* Stubbed until the underlying protocol stacks exist.
|
|
6
|
+
*/
|
|
7
|
+
import type { TestCaseId, TestResult } from "../types.js";
|
|
8
|
+
/** Run the TLS 1.3 (RFC 8446) compliance checks. */
|
|
9
|
+
export declare function runTlsCompliance(_id?: TestCaseId): TestResult;
|
|
10
|
+
/** Run the HTTP/2 (RFC 9113) compliance checks. */
|
|
11
|
+
export declare function runHttp2Compliance(_id?: TestCaseId): TestResult;
|
|
12
|
+
/** Run the HTTP/1.1 (RFC 9110) compliance checks. */
|
|
13
|
+
export declare function runHttp1Compliance(_id?: TestCaseId): TestResult;
|
|
14
|
+
//# sourceMappingURL=rfcTests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rfcTests.d.ts","sourceRoot":"","sources":["../../src/rfc/rfcTests.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1D,oDAAoD;AACpD,wBAAgB,gBAAgB,CAC5B,GAAG,GAAE,UAAiC,GACvC,UAAU,CAGZ;AAED,mDAAmD;AACnD,wBAAgB,kBAAkB,CAC9B,GAAG,GAAE,UAAmC,GACzC,UAAU,CAGZ;AAED,qDAAqD;AACrD,wBAAgB,kBAAkB,CAC9B,GAAG,GAAE,UAAmC,GACzC,UAAU,CAGZ"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC compliance test suites.
|
|
3
|
+
*
|
|
4
|
+
* Each function runs a focused compliance check and returns a {@link TestResult}.
|
|
5
|
+
* Stubbed until the underlying protocol stacks exist.
|
|
6
|
+
*/
|
|
7
|
+
/** Run the TLS 1.3 (RFC 8446) compliance checks. */
|
|
8
|
+
export function runTlsCompliance(_id = "tls_rfc") {
|
|
9
|
+
void _id;
|
|
10
|
+
throw new Error("not implemented — see PLAN.md");
|
|
11
|
+
}
|
|
12
|
+
/** Run the HTTP/2 (RFC 9113) compliance checks. */
|
|
13
|
+
export function runHttp2Compliance(_id = "http2_rfc") {
|
|
14
|
+
void _id;
|
|
15
|
+
throw new Error("not implemented — see PLAN.md");
|
|
16
|
+
}
|
|
17
|
+
/** Run the HTTP/1.1 (RFC 9110) compliance checks. */
|
|
18
|
+
export function runHttp1Compliance(_id = "http1_rfc") {
|
|
19
|
+
void _id;
|
|
20
|
+
throw new Error("not implemented — see PLAN.md");
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=rfcTests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rfcTests.js","sourceRoot":"","sources":["../../src/rfc/rfcTests.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,oDAAoD;AACpD,MAAM,UAAU,gBAAgB,CAC5B,MAAmB,SAAmB;IAEtC,KAAK,GAAG,CAAC;IACT,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;AACrD,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,kBAAkB,CAC9B,MAAmB,WAAqB;IAExC,KAAK,GAAG,CAAC;IACT,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;AACrD,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,kBAAkB,CAC9B,MAAmB,WAAqB;IAExC,KAAK,GAAG,CAAC;IACT,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;AACrD,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain types for @browsercore/testing.
|
|
3
|
+
*
|
|
4
|
+
* Protocol verification: RFC compliance tests, browser golden packet captures,
|
|
5
|
+
* integration tests, and benchmarking. Compares generated packets against
|
|
6
|
+
* captures from real browsers.
|
|
7
|
+
*/
|
|
8
|
+
/** Branded test-case identifier. */
|
|
9
|
+
export type TestCaseId = string & {
|
|
10
|
+
__brand: "TestCaseId";
|
|
11
|
+
};
|
|
12
|
+
/** Branded golden-capture identifier. */
|
|
13
|
+
export type CaptureId = string & {
|
|
14
|
+
__brand: "CaptureId";
|
|
15
|
+
};
|
|
16
|
+
/** Known browser capture sources. */
|
|
17
|
+
export type CaptureSource = "chrome-140" | "firefox-135" | "safari-18" | "edge-140";
|
|
18
|
+
/** Which protocol layer a capture records. */
|
|
19
|
+
export type CaptureProtocol = "tls" | "http2" | "http1" | "tcp";
|
|
20
|
+
/** A single runnable test case. */
|
|
21
|
+
export interface TestCase {
|
|
22
|
+
readonly id: TestCaseId;
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly description: string;
|
|
25
|
+
run(): Promise<TestResult>;
|
|
26
|
+
}
|
|
27
|
+
/** Outcome of running a single {@link TestCase}. */
|
|
28
|
+
export interface TestResult {
|
|
29
|
+
readonly id: TestCaseId;
|
|
30
|
+
readonly pass: boolean;
|
|
31
|
+
/** Actual bytes / value produced. */
|
|
32
|
+
readonly actual: unknown;
|
|
33
|
+
/** Expected bytes / value. */
|
|
34
|
+
readonly expected: unknown;
|
|
35
|
+
/** Human-readable diff when `pass === false`. */
|
|
36
|
+
readonly diff?: string;
|
|
37
|
+
}
|
|
38
|
+
/** A golden packet capture recorded from a real browser. */
|
|
39
|
+
export interface GoldenCapture {
|
|
40
|
+
readonly id: CaptureId;
|
|
41
|
+
readonly source: CaptureSource;
|
|
42
|
+
readonly protocol: CaptureProtocol;
|
|
43
|
+
/** Raw bytes as seen on the wire. */
|
|
44
|
+
readonly bytes: Uint8Array;
|
|
45
|
+
readonly description: string;
|
|
46
|
+
}
|
|
47
|
+
/** Result of comparing actual bytes against a golden capture. */
|
|
48
|
+
export interface ComparisonResult {
|
|
49
|
+
readonly matches: boolean;
|
|
50
|
+
/** First byte index where actual diverges from expected, if any. */
|
|
51
|
+
readonly divergenceByteIndex: number | undefined;
|
|
52
|
+
readonly message: string;
|
|
53
|
+
}
|
|
54
|
+
/** Summary statistics for a benchmark run. */
|
|
55
|
+
export interface BenchStats {
|
|
56
|
+
readonly iterations: number;
|
|
57
|
+
readonly avgMs: number;
|
|
58
|
+
readonly p50: number;
|
|
59
|
+
readonly p95: number;
|
|
60
|
+
readonly p99: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The 17 test categories from docs/TEST-SUITE.md.
|
|
64
|
+
*
|
|
65
|
+
* Defined as a `const` object so the id set is the single source of truth and
|
|
66
|
+
* `TestCategoryId` is derived from it — never a bare string (see
|
|
67
|
+
* CODING_STANDARDS.md §17 "No magic strings").
|
|
68
|
+
*/
|
|
69
|
+
export declare const TestCategory: {
|
|
70
|
+
readonly TcpTransport: "tcp-transport";
|
|
71
|
+
readonly TlsSerialization: "tls-serialization";
|
|
72
|
+
readonly BrowserProfileComparison: "browser-profile-comparison";
|
|
73
|
+
readonly TlsFingerprint: "tls-fingerprint";
|
|
74
|
+
readonly Http1: "http1";
|
|
75
|
+
readonly Http2: "http2";
|
|
76
|
+
readonly HeaderProfiles: "header-profiles";
|
|
77
|
+
readonly CookieBehavior: "cookie-behavior";
|
|
78
|
+
readonly Compression: "compression";
|
|
79
|
+
readonly RedirectHandling: "redirect-handling";
|
|
80
|
+
readonly SessionResumption: "session-resumption";
|
|
81
|
+
readonly ConnectionReuse: "connection-reuse";
|
|
82
|
+
readonly ErrorHandling: "error-handling";
|
|
83
|
+
readonly PacketCaptureComparison: "packet-capture-comparison";
|
|
84
|
+
readonly RealWorldCompatibility: "real-world-compatibility";
|
|
85
|
+
readonly Regression: "regression";
|
|
86
|
+
readonly PerformanceBenchmarks: "performance-benchmarks";
|
|
87
|
+
};
|
|
88
|
+
/** One of the 17 known test-category ids. */
|
|
89
|
+
export type TestCategoryId = (typeof TestCategory)[keyof typeof TestCategory];
|
|
90
|
+
/** Lifecycle status of a spec test case in the plan. */
|
|
91
|
+
export type TestStatus = "planned" | "implemented" | "skipped";
|
|
92
|
+
/**
|
|
93
|
+
* A single test case as recorded in the spec plan.
|
|
94
|
+
*
|
|
95
|
+
* Distinct from the runnable {@link TestCase} interface (which carries a
|
|
96
|
+
* `run()` method); this is a static plan/record entry.
|
|
97
|
+
*/
|
|
98
|
+
export interface SpecTestCase {
|
|
99
|
+
readonly id: TestCaseId;
|
|
100
|
+
readonly category: TestCategoryId;
|
|
101
|
+
readonly name: string;
|
|
102
|
+
readonly description: string;
|
|
103
|
+
readonly status: TestStatus;
|
|
104
|
+
}
|
|
105
|
+
/** A recorded run of spec test cases plus their outcomes. */
|
|
106
|
+
export interface TestRun {
|
|
107
|
+
readonly id: string;
|
|
108
|
+
readonly timestamp: string;
|
|
109
|
+
readonly cases: readonly SpecTestCase[];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A byte range within a capture that is intentionally randomized by the
|
|
113
|
+
* protocol spec and so MUST be masked before golden comparison (Cat 14).
|
|
114
|
+
*/
|
|
115
|
+
export interface RandomizedField {
|
|
116
|
+
readonly byteOffset: number;
|
|
117
|
+
readonly length: number;
|
|
118
|
+
readonly reason: "ephemeral_key" | "nonce" | "grease" | "random";
|
|
119
|
+
}
|
|
120
|
+
/** Metadata describing a stored golden capture (sidecar to its `.bin`). */
|
|
121
|
+
export interface CaptureMeta {
|
|
122
|
+
readonly source: "curl-impersonate" | "real-browser";
|
|
123
|
+
readonly profile: import("@browsercore/profiles").ProfileId;
|
|
124
|
+
readonly protocol: CaptureProtocol;
|
|
125
|
+
readonly record: "client_hello" | "settings" | "headers" | "server_hello";
|
|
126
|
+
readonly description: string;
|
|
127
|
+
readonly randomizedFields: readonly RandomizedField[];
|
|
128
|
+
readonly createdAt: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Result of a byte comparison that supports an ignore-list of randomized
|
|
132
|
+
* ranges. Extends {@link ComparisonResult} with the ranges that were masked.
|
|
133
|
+
*/
|
|
134
|
+
export interface ComparisonResultWithIgnore {
|
|
135
|
+
readonly matches: boolean;
|
|
136
|
+
readonly divergenceByteIndex: number | undefined;
|
|
137
|
+
readonly message: string;
|
|
138
|
+
readonly maskedRanges: readonly RandomizedField[];
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain types for @browsercore/testing.
|
|
3
|
+
*
|
|
4
|
+
* Protocol verification: RFC compliance tests, browser golden packet captures,
|
|
5
|
+
* integration tests, and benchmarking. Compares generated packets against
|
|
6
|
+
* captures from real browsers.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The 17 test categories from docs/TEST-SUITE.md.
|
|
10
|
+
*
|
|
11
|
+
* Defined as a `const` object so the id set is the single source of truth and
|
|
12
|
+
* `TestCategoryId` is derived from it — never a bare string (see
|
|
13
|
+
* CODING_STANDARDS.md §17 "No magic strings").
|
|
14
|
+
*/
|
|
15
|
+
export const TestCategory = {
|
|
16
|
+
TcpTransport: "tcp-transport",
|
|
17
|
+
TlsSerialization: "tls-serialization",
|
|
18
|
+
BrowserProfileComparison: "browser-profile-comparison",
|
|
19
|
+
TlsFingerprint: "tls-fingerprint",
|
|
20
|
+
Http1: "http1",
|
|
21
|
+
Http2: "http2",
|
|
22
|
+
HeaderProfiles: "header-profiles",
|
|
23
|
+
CookieBehavior: "cookie-behavior",
|
|
24
|
+
Compression: "compression",
|
|
25
|
+
RedirectHandling: "redirect-handling",
|
|
26
|
+
SessionResumption: "session-resumption",
|
|
27
|
+
ConnectionReuse: "connection-reuse",
|
|
28
|
+
ErrorHandling: "error-handling",
|
|
29
|
+
PacketCaptureComparison: "packet-capture-comparison",
|
|
30
|
+
RealWorldCompatibility: "real-world-compatibility",
|
|
31
|
+
Regression: "regression",
|
|
32
|
+
PerformanceBenchmarks: "performance-benchmarks",
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IACxB,YAAY,EAAE,eAAe;IAC7B,gBAAgB,EAAE,mBAAmB;IACrC,wBAAwB,EAAE,4BAA4B;IACtD,cAAc,EAAE,iBAAiB;IACjC,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,cAAc,EAAE,iBAAiB;IACjC,cAAc,EAAE,iBAAiB;IACjC,WAAW,EAAE,aAAa;IAC1B,gBAAgB,EAAE,mBAAmB;IACrC,iBAAiB,EAAE,oBAAoB;IACvC,eAAe,EAAE,kBAAkB;IACnC,aAAa,EAAE,gBAAgB;IAC/B,uBAAuB,EAAE,2BAA2B;IACpD,sBAAsB,EAAE,0BAA0B;IAClD,UAAU,EAAE,YAAY;IACxB,qBAAqB,EAAE,wBAAwB;CACzC,CAAC"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small shared helpers for @browsercore/testing.
|
|
3
|
+
*/
|
|
4
|
+
import type { ComparisonResult, ComparisonResultWithIgnore, RandomizedField } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Exhaustiveness check for `switch`/`if-else` over discriminated unions.
|
|
7
|
+
* Call in the `default` branch: `default: assertNever(x)`.
|
|
8
|
+
* Adding a new union member forces every handler to compile-error until handled.
|
|
9
|
+
*/
|
|
10
|
+
export declare function assertNever(x: never): never;
|
|
11
|
+
/** Generate a unique TestCaseId (not cryptographically random). */
|
|
12
|
+
export declare function createId(prefix?: "tc"): string;
|
|
13
|
+
/** Format bytes as a lowercase hex string with no separators. */
|
|
14
|
+
export declare function bytesToHex(buf: Uint8Array): string;
|
|
15
|
+
/**
|
|
16
|
+
* Compare two byte arrays. Returns a {@link ComparisonResult} reporting whether
|
|
17
|
+
* they match, the first divergence index (if any), and a message.
|
|
18
|
+
*/
|
|
19
|
+
export declare function compareBytes(a: Uint8Array, b: Uint8Array): ComparisonResult;
|
|
20
|
+
/**
|
|
21
|
+
* Compare two byte arrays while masking byte ranges that are intentionally
|
|
22
|
+
* randomized by the protocol spec (ephemeral keys, nonces, GREASE, random).
|
|
23
|
+
*
|
|
24
|
+
* Ranges in `ignore` are skipped when scanning for divergence. The returned
|
|
25
|
+
* {@link ComparisonResultWithIgnore.maskedReports} lists the ranges that were
|
|
26
|
+
* masked so callers can audit what was excluded.
|
|
27
|
+
*/
|
|
28
|
+
export declare function compareBytesWithIgnore(a: Uint8Array, b: Uint8Array, ignore: readonly RandomizedField[]): ComparisonResultWithIgnore;
|
|
29
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACR,gBAAgB,EAChB,0BAA0B,EAC1B,eAAe,EAClB,MAAM,YAAY,CAAC;AAEpB;;;;GAIG;AAEH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAE3C;AAED,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,MAAM,GAAE,IAAW,GAAG,MAAM,CAEpD;AAED,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAMlD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,gBAAgB,CAsB3E;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAClC,CAAC,EAAE,UAAU,EACb,CAAC,EAAE,UAAU,EACb,MAAM,EAAE,SAAS,eAAe,EAAE,GACnC,0BAA0B,CAwC5B"}
|