@vitest/browser 4.0.15 → 4.0.17
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/dist/client/.vite/manifest.json +2 -2
- package/dist/client/__vitest__/assets/index-BUCFJtth.js +57 -0
- package/dist/client/__vitest__/index.html +1 -1
- package/dist/client/__vitest_browser__/{orchestrator-8U3FyXSU.js → orchestrator-S_3e_uzt.js} +40 -8
- package/dist/client/__vitest_browser__/{tester-CrGChK3u.js → tester-k74mgIRa.js} +63 -36
- package/dist/client/error-catcher.js +5 -1
- package/dist/client/esm-client-injector.js +2 -1
- package/dist/client/orchestrator.html +1 -1
- package/dist/client/tester/tester.html +1 -1
- package/dist/client.js +197 -211
- package/dist/expect-element.js +3 -3
- package/dist/{index-CEutxZap.js → index-D6m36C6U.js} +1 -1
- package/dist/index.js +403 -307
- package/dist/locators.js +1 -1
- package/dist/shared/screenshotMatcher/types.d.ts +1 -1
- package/package.json +9 -8
- package/dist/client/__vitest__/assets/index-CfDzoXo3.js +0 -57
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { PNG } from 'pngjs';
|
|
|
18
18
|
import pm from 'pixelmatch';
|
|
19
19
|
import { WebSocketServer } from 'ws';
|
|
20
20
|
|
|
21
|
-
var version = "4.0.
|
|
21
|
+
var version = "4.0.17";
|
|
22
22
|
|
|
23
23
|
const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
|
|
24
24
|
function normalizeWindowsPath(input = "") {
|
|
@@ -695,6 +695,7 @@ async function resolveOrchestrator(globalServer, url, res) {
|
|
|
695
695
|
__VITEST_TYPE__: "\"orchestrator\"",
|
|
696
696
|
__VITEST_SESSION_ID__: JSON.stringify(sessionId),
|
|
697
697
|
__VITEST_TESTER_ID__: "\"none\"",
|
|
698
|
+
__VITEST_OTEL_CARRIER__: url.searchParams.get("otelCarrier") ?? "null",
|
|
698
699
|
__VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(browserProject.project.getProvidedContext())),
|
|
699
700
|
__VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
|
|
700
701
|
});
|
|
@@ -790,6 +791,7 @@ async function resolveTester(globalServer, url, res, next) {
|
|
|
790
791
|
__VITEST_TYPE__: "\"tester\"",
|
|
791
792
|
__VITEST_METHOD__: JSON.stringify("none"),
|
|
792
793
|
__VITEST_SESSION_ID__: JSON.stringify(sessionId),
|
|
794
|
+
__VITEST_OTEL_CARRIER__: JSON.stringify(null),
|
|
793
795
|
__VITEST_TESTER_ID__: JSON.stringify(crypto.randomUUID()),
|
|
794
796
|
__VITEST_PROVIDED_CONTEXT__: "{}",
|
|
795
797
|
__VITEST_API_TOKEN__: JSON.stringify(globalServer.vitest.config.api.token)
|
|
@@ -1139,6 +1141,11 @@ var BrowserPlugin = (parentServer, base = "/") => {
|
|
|
1139
1141
|
if (vueTestUtils) {
|
|
1140
1142
|
include.push("@vue/test-utils");
|
|
1141
1143
|
}
|
|
1144
|
+
const otelConfig = project.config.experimental.openTelemetry;
|
|
1145
|
+
if (otelConfig?.enabled && otelConfig.browserSdkPath) {
|
|
1146
|
+
entries.push(otelConfig.browserSdkPath);
|
|
1147
|
+
include.push("@opentelemetry/api");
|
|
1148
|
+
}
|
|
1142
1149
|
return {
|
|
1143
1150
|
define,
|
|
1144
1151
|
resolve: { dedupe: ["vitest"] },
|
|
@@ -1378,6 +1385,16 @@ body {
|
|
|
1378
1385
|
};
|
|
1379
1386
|
return { optimizeDeps: rolldownVersion ? { rolldownOptions: { plugins: [rolldownPlugin] } } : { esbuildOptions: { plugins: [esbuildPlugin] } } };
|
|
1380
1387
|
}
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
name: "vitest:browser:__vitest_browser_import_meta_env_init__",
|
|
1391
|
+
transform: { handler(code) {
|
|
1392
|
+
// this transform runs after `vitest:meta-env-replacer` so that
|
|
1393
|
+
// `import.meta.env` will be handled by Vite import analysis to match behavior.
|
|
1394
|
+
if (code.includes("__vitest_browser_import_meta_env_init__")) {
|
|
1395
|
+
return code.replace("__vitest_browser_import_meta_env_init__", "import.meta.env");
|
|
1396
|
+
}
|
|
1397
|
+
} }
|
|
1381
1398
|
}
|
|
1382
1399
|
];
|
|
1383
1400
|
};
|
|
@@ -2214,9 +2231,20 @@ function asyncTimeout(timeout) {
|
|
|
2214
2231
|
});
|
|
2215
2232
|
}
|
|
2216
2233
|
|
|
2234
|
+
/**
|
|
2235
|
+
* Browser command that compares a screenshot against a stored reference.
|
|
2236
|
+
*
|
|
2237
|
+
* The comparison workflow is organized as follows:
|
|
2238
|
+
*
|
|
2239
|
+
* 1. Load existing reference (if any)
|
|
2240
|
+
* 2. Capture a stable screenshot (retrying until the page stops changing)
|
|
2241
|
+
* 3. Determine the outcome based on capture results and update settings
|
|
2242
|
+
* 4. Write any necessary files (new references, diffs)
|
|
2243
|
+
* 5. Return result for the test runner
|
|
2244
|
+
*/
|
|
2217
2245
|
const screenshotMatcher = async (context, name, testName, options) => {
|
|
2218
2246
|
if (!context.testPath) {
|
|
2219
|
-
throw new Error(
|
|
2247
|
+
throw new Error("Cannot compare screenshots without a test path");
|
|
2220
2248
|
}
|
|
2221
2249
|
const { element } = options;
|
|
2222
2250
|
const { codec, comparator, paths, resolvedOptions: { comparatorOptions, screenshotOptions, timeout } } = resolveOptions({
|
|
@@ -2226,9 +2254,8 @@ const screenshotMatcher = async (context, name, testName, options) => {
|
|
|
2226
2254
|
options
|
|
2227
2255
|
});
|
|
2228
2256
|
const referenceFile = await readFile$1(paths.reference).catch(() => null);
|
|
2229
|
-
const reference = referenceFile && await codec.decode(
|
|
2230
|
-
const
|
|
2231
|
-
const stableScreenshot = getStableScreenshots({
|
|
2257
|
+
const reference = referenceFile && await codec.decode(referenceFile, {});
|
|
2258
|
+
const screenshotResult = await waitForStableScreenshot({
|
|
2232
2259
|
codec,
|
|
2233
2260
|
comparator,
|
|
2234
2261
|
comparatorOptions,
|
|
@@ -2236,115 +2263,189 @@ const screenshotMatcher = async (context, name, testName, options) => {
|
|
|
2236
2263
|
element,
|
|
2237
2264
|
name: `${Date.now()}-${basename(paths.reference)}`,
|
|
2238
2265
|
reference,
|
|
2239
|
-
screenshotOptions
|
|
2240
|
-
|
|
2266
|
+
screenshotOptions
|
|
2267
|
+
}, timeout);
|
|
2268
|
+
const outcome = await determineOutcome({
|
|
2269
|
+
reference,
|
|
2270
|
+
screenshot: screenshotResult && screenshotResult.actual,
|
|
2271
|
+
retries: screenshotResult?.retries ?? 0,
|
|
2272
|
+
updateSnapshot: context.project.serializedConfig.snapshotOptions.updateSnapshot,
|
|
2273
|
+
paths,
|
|
2274
|
+
comparator,
|
|
2275
|
+
comparatorOptions
|
|
2241
2276
|
});
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2277
|
+
await performSideEffects(outcome, codec);
|
|
2278
|
+
return buildOutput(outcome, timeout);
|
|
2279
|
+
};
|
|
2280
|
+
/**
|
|
2281
|
+
* Core comparison logic that produces a {@linkcode MatchOutcome}.
|
|
2282
|
+
*
|
|
2283
|
+
* All branching logic lives here. This is the single source of truth for "what happened".
|
|
2284
|
+
*
|
|
2285
|
+
* The outcome carries all data needed by {@linkcode performSideEffects} and {@linkcode buildOutput}.
|
|
2286
|
+
*/
|
|
2287
|
+
async function determineOutcome({ comparator, comparatorOptions, paths, reference, retries, screenshot, updateSnapshot }) {
|
|
2288
|
+
if (screenshot === null) {
|
|
2249
2289
|
return {
|
|
2250
|
-
|
|
2251
|
-
reference:
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
},
|
|
2256
|
-
actual: null,
|
|
2257
|
-
diff: null,
|
|
2258
|
-
message: `Could not capture a stable screenshot within ${timeout}ms.`
|
|
2290
|
+
type: "unstable-screenshot",
|
|
2291
|
+
reference: reference && {
|
|
2292
|
+
image: reference,
|
|
2293
|
+
path: paths.reference
|
|
2294
|
+
}
|
|
2259
2295
|
};
|
|
2260
2296
|
}
|
|
2261
|
-
|
|
2262
|
-
// if there's no reference or if we want to update snapshots, we have to finish the comparison early
|
|
2297
|
+
// no reference to compare against - create one based on update settings
|
|
2263
2298
|
if (reference === null || updateSnapshot === "all") {
|
|
2264
|
-
|
|
2265
|
-
const referencePath = shouldCreateReference ? paths.reference : paths.diffs.reference;
|
|
2266
|
-
await writeScreenshot(referencePath, await codec.encode(value.actual, {}));
|
|
2267
|
-
// case #02
|
|
2268
|
-
// - got a stable screenshot, but there is no reference and we don't want to update screenshots
|
|
2269
|
-
// - fail
|
|
2270
|
-
if (updateSnapshot !== "all") {
|
|
2299
|
+
if (updateSnapshot === "all") {
|
|
2271
2300
|
return {
|
|
2272
|
-
|
|
2301
|
+
type: "update-reference",
|
|
2273
2302
|
reference: {
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
},
|
|
2278
|
-
actual: null,
|
|
2279
|
-
diff: null,
|
|
2280
|
-
message: `No existing reference screenshot found${shouldCreateReference ? "; a new one was created. Review it before running tests again." : "."}`
|
|
2303
|
+
image: screenshot,
|
|
2304
|
+
path: paths.reference
|
|
2305
|
+
}
|
|
2281
2306
|
};
|
|
2282
2307
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2308
|
+
const location = updateSnapshot === "none" ? "diffs" : "reference";
|
|
2309
|
+
return {
|
|
2310
|
+
type: "missing-reference",
|
|
2311
|
+
location,
|
|
2312
|
+
reference: {
|
|
2313
|
+
image: screenshot,
|
|
2314
|
+
path: location === "reference" ? paths.reference : paths.diffs.reference
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
// first capture matched reference (used as baseline) - no further comparison needed
|
|
2319
|
+
if (retries === 0) {
|
|
2320
|
+
return { type: "matched-immediately" };
|
|
2321
|
+
}
|
|
2322
|
+
const comparisonResult = await comparator(reference, screenshot, {
|
|
2295
2323
|
createDiff: true,
|
|
2296
2324
|
...comparatorOptions
|
|
2297
2325
|
});
|
|
2298
|
-
if (
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
metadata: {
|
|
2302
|
-
height: reference.metadata.height,
|
|
2303
|
-
width: reference.metadata.width
|
|
2304
|
-
}
|
|
2305
|
-
}, {});
|
|
2306
|
-
await writeScreenshot(paths.diffs.diff, diff);
|
|
2307
|
-
}
|
|
2308
|
-
// case #05
|
|
2309
|
-
// - reference matches stable screenshot
|
|
2310
|
-
// - pass
|
|
2311
|
-
if (finalResult.pass === true) {
|
|
2312
|
-
return { pass: true };
|
|
2313
|
-
}
|
|
2314
|
-
const actual = await codec.encode(value.actual, {});
|
|
2315
|
-
await writeScreenshot(paths.diffs.actual, actual);
|
|
2316
|
-
// case #06
|
|
2317
|
-
// - fallback, reference does NOT match stable screenshot
|
|
2318
|
-
// - fail
|
|
2326
|
+
if (comparisonResult.pass) {
|
|
2327
|
+
return { type: "matched-after-comparison" };
|
|
2328
|
+
}
|
|
2319
2329
|
return {
|
|
2320
|
-
|
|
2330
|
+
type: "mismatch",
|
|
2321
2331
|
reference: {
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
height: reference.metadata.height
|
|
2332
|
+
image: reference,
|
|
2333
|
+
path: paths.reference
|
|
2325
2334
|
},
|
|
2326
2335
|
actual: {
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
height: value.actual.metadata.height
|
|
2336
|
+
image: screenshot,
|
|
2337
|
+
path: paths.diffs.actual
|
|
2330
2338
|
},
|
|
2331
|
-
diff:
|
|
2332
|
-
|
|
2339
|
+
diff: comparisonResult.diff && {
|
|
2340
|
+
image: {
|
|
2341
|
+
data: comparisonResult.diff,
|
|
2342
|
+
metadata: reference.metadata
|
|
2343
|
+
},
|
|
2344
|
+
path: paths.diffs.diff
|
|
2345
|
+
},
|
|
2346
|
+
message: comparisonResult.message
|
|
2333
2347
|
};
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Writes files to disk based on the outcome.
|
|
2351
|
+
*
|
|
2352
|
+
* Only `missing-reference`, `update-reference`, and `mismatch` write files. Successful matches produce no side effects.
|
|
2353
|
+
*/
|
|
2354
|
+
async function performSideEffects(outcome, codec) {
|
|
2355
|
+
switch (outcome.type) {
|
|
2356
|
+
case "missing-reference":
|
|
2357
|
+
case "update-reference": {
|
|
2358
|
+
await writeScreenshot(outcome.reference.path, await codec.encode(outcome.reference.image, {}));
|
|
2359
|
+
break;
|
|
2360
|
+
}
|
|
2361
|
+
case "mismatch": {
|
|
2362
|
+
await writeScreenshot(outcome.actual.path, await codec.encode(outcome.actual.image, {}));
|
|
2363
|
+
if (outcome.diff) {
|
|
2364
|
+
await writeScreenshot(outcome.diff.path, await codec.encode(outcome.diff.image, {}));
|
|
2365
|
+
}
|
|
2366
|
+
break;
|
|
2367
|
+
}
|
|
2341
2368
|
}
|
|
2342
2369
|
}
|
|
2343
2370
|
/**
|
|
2371
|
+
* Transforms a {@linkcode MatchOutcome} into the output format expected by the test runner.
|
|
2372
|
+
*
|
|
2373
|
+
* Maps each outcome to a pass/fail result with metadata and error messages.
|
|
2374
|
+
*/
|
|
2375
|
+
function buildOutput(outcome, timeout) {
|
|
2376
|
+
switch (outcome.type) {
|
|
2377
|
+
case "unstable-screenshot": return {
|
|
2378
|
+
pass: false,
|
|
2379
|
+
reference: outcome.reference && {
|
|
2380
|
+
path: outcome.reference.path,
|
|
2381
|
+
width: outcome.reference.image.metadata.width,
|
|
2382
|
+
height: outcome.reference.image.metadata.height
|
|
2383
|
+
},
|
|
2384
|
+
actual: null,
|
|
2385
|
+
diff: null,
|
|
2386
|
+
message: `Could not capture a stable screenshot within ${timeout}ms.`
|
|
2387
|
+
};
|
|
2388
|
+
case "missing-reference": {
|
|
2389
|
+
return {
|
|
2390
|
+
pass: false,
|
|
2391
|
+
reference: {
|
|
2392
|
+
path: outcome.reference.path,
|
|
2393
|
+
width: outcome.reference.image.metadata.width,
|
|
2394
|
+
height: outcome.reference.image.metadata.height
|
|
2395
|
+
},
|
|
2396
|
+
actual: null,
|
|
2397
|
+
diff: null,
|
|
2398
|
+
message: outcome.location === "reference" ? "No existing reference screenshot found; a new one was created. Review it before running tests again." : "No existing reference screenshot found."
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
case "update-reference":
|
|
2402
|
+
case "matched-immediately":
|
|
2403
|
+
case "matched-after-comparison": return { pass: true };
|
|
2404
|
+
case "mismatch": return {
|
|
2405
|
+
pass: false,
|
|
2406
|
+
reference: {
|
|
2407
|
+
path: outcome.reference.path,
|
|
2408
|
+
width: outcome.reference.image.metadata.width,
|
|
2409
|
+
height: outcome.reference.image.metadata.height
|
|
2410
|
+
},
|
|
2411
|
+
actual: {
|
|
2412
|
+
path: outcome.actual.path,
|
|
2413
|
+
width: outcome.actual.image.metadata.width,
|
|
2414
|
+
height: outcome.actual.image.metadata.height
|
|
2415
|
+
},
|
|
2416
|
+
diff: outcome.diff && {
|
|
2417
|
+
path: outcome.diff.path,
|
|
2418
|
+
width: outcome.diff.image.metadata.width,
|
|
2419
|
+
height: outcome.diff.image.metadata.height
|
|
2420
|
+
},
|
|
2421
|
+
message: `Screenshot does not match the stored reference.${outcome.message ? `\n${outcome.message}` : ""}`
|
|
2422
|
+
};
|
|
2423
|
+
default: {
|
|
2424
|
+
return {
|
|
2425
|
+
pass: false,
|
|
2426
|
+
actual: null,
|
|
2427
|
+
reference: null,
|
|
2428
|
+
diff: null,
|
|
2429
|
+
message: `Outcome (${outcome.type}) not handled. This is a bug in Vitest. Please, open an issue with reproduction.`
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
/**
|
|
2435
|
+
* Captures a stable screenshot with timeout handling.
|
|
2436
|
+
*
|
|
2437
|
+
* Wraps {@linkcode getStableScreenshot} with an abort controller that triggers when the timeout expires. Returns `null` if the page never stabilizes.
|
|
2438
|
+
*/
|
|
2439
|
+
async function waitForStableScreenshot(options, timeout) {
|
|
2440
|
+
const abortController = new AbortController();
|
|
2441
|
+
const stableScreenshot = getStableScreenshot(options, abortController.signal);
|
|
2442
|
+
const result = await (timeout === 0 ? stableScreenshot : Promise.race([stableScreenshot, asyncTimeout(timeout).finally(() => abortController.abort())]));
|
|
2443
|
+
return result;
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2344
2446
|
* Takes screenshots repeatedly until the page reaches a visually stable state.
|
|
2345
2447
|
*
|
|
2346
|
-
* This function compares consecutive screenshots and continues taking new ones
|
|
2347
|
-
* until two consecutive screenshots match according to the provided comparator.
|
|
2448
|
+
* This function compares consecutive screenshots and continues taking new ones until two consecutive screenshots match according to the provided comparator.
|
|
2348
2449
|
*
|
|
2349
2450
|
* The process works as follows:
|
|
2350
2451
|
*
|
|
@@ -2354,10 +2455,9 @@ async function writeScreenshot(path, image) {
|
|
|
2354
2455
|
* 4. If they don't match, it continues with the newer screenshot as the baseline
|
|
2355
2456
|
* 5. Repeats until stability is achieved or the operation is aborted
|
|
2356
2457
|
*
|
|
2357
|
-
* @returns `Promise` resolving to an object containing the retry count and
|
|
2358
|
-
* final screenshot
|
|
2458
|
+
* @returns `Promise` resolving to an object containing the retry count and final screenshot
|
|
2359
2459
|
*/
|
|
2360
|
-
async function
|
|
2460
|
+
async function getStableScreenshot({ codec, context, comparator, comparatorOptions, element, name, reference, screenshotOptions }, signal) {
|
|
2361
2461
|
const screenshotArgument = {
|
|
2362
2462
|
codec,
|
|
2363
2463
|
context,
|
|
@@ -2372,12 +2472,12 @@ async function getStableScreenshots({ codec, context, comparator, comparatorOpti
|
|
|
2372
2472
|
decodedBaseline = takeDecodedScreenshot(screenshotArgument);
|
|
2373
2473
|
}
|
|
2374
2474
|
const [image1, image2] = await Promise.all([decodedBaseline, takeDecodedScreenshot(screenshotArgument)]);
|
|
2375
|
-
const
|
|
2475
|
+
const isStable = (await comparator(image1, image2, {
|
|
2376
2476
|
...comparatorOptions,
|
|
2377
2477
|
createDiff: false
|
|
2378
2478
|
})).pass;
|
|
2379
2479
|
decodedBaseline = image2;
|
|
2380
|
-
if (
|
|
2480
|
+
if (isStable) {
|
|
2381
2481
|
break;
|
|
2382
2482
|
}
|
|
2383
2483
|
retries += 1;
|
|
@@ -2387,6 +2487,15 @@ async function getStableScreenshots({ codec, context, comparator, comparatorOpti
|
|
|
2387
2487
|
actual: await decodedBaseline
|
|
2388
2488
|
};
|
|
2389
2489
|
}
|
|
2490
|
+
/** Writes encoded images to disk, creating parent directories as needed. */
|
|
2491
|
+
async function writeScreenshot(path, image) {
|
|
2492
|
+
try {
|
|
2493
|
+
await mkdir(dirname(path), { recursive: true });
|
|
2494
|
+
await writeFile$1(path, image);
|
|
2495
|
+
} catch (cause) {
|
|
2496
|
+
throw new Error("Couldn't write file to fs", { cause });
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2390
2499
|
|
|
2391
2500
|
var builtinCommands = {
|
|
2392
2501
|
readFile,
|
|
@@ -2680,223 +2789,209 @@ class ParentBrowserProject {
|
|
|
2680
2789
|
}
|
|
2681
2790
|
}
|
|
2682
2791
|
|
|
2792
|
+
//#region src/messages.ts
|
|
2683
2793
|
const TYPE_REQUEST = "q";
|
|
2684
2794
|
const TYPE_RESPONSE = "s";
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
}
|
|
2689
|
-
const defaultDeserialize = defaultSerialize;
|
|
2690
|
-
const { clearTimeout, setTimeout: setTimeout$1 } = globalThis;
|
|
2691
|
-
const random = Math.random.bind(Math);
|
|
2692
|
-
function createBirpc($functions, options) {
|
|
2693
|
-
const {
|
|
2694
|
-
post,
|
|
2695
|
-
on,
|
|
2696
|
-
off = () => {
|
|
2697
|
-
},
|
|
2698
|
-
eventNames = [],
|
|
2699
|
-
serialize = defaultSerialize,
|
|
2700
|
-
deserialize = defaultDeserialize,
|
|
2701
|
-
resolver,
|
|
2702
|
-
bind = "rpc",
|
|
2703
|
-
timeout = DEFAULT_TIMEOUT
|
|
2704
|
-
} = options;
|
|
2705
|
-
let $closed = false;
|
|
2706
|
-
const _rpcPromiseMap = /* @__PURE__ */ new Map();
|
|
2707
|
-
let _promiseInit;
|
|
2708
|
-
async function _call(method, args, event, optional) {
|
|
2709
|
-
if ($closed)
|
|
2710
|
-
throw new Error(`[birpc] rpc is closed, cannot call "${method}"`);
|
|
2711
|
-
const req = { m: method, a: args, t: TYPE_REQUEST };
|
|
2712
|
-
if (optional)
|
|
2713
|
-
req.o = true;
|
|
2714
|
-
const send = async (_req) => post(serialize(_req));
|
|
2715
|
-
if (event) {
|
|
2716
|
-
await send(req);
|
|
2717
|
-
return;
|
|
2718
|
-
}
|
|
2719
|
-
if (_promiseInit) {
|
|
2720
|
-
try {
|
|
2721
|
-
await _promiseInit;
|
|
2722
|
-
} finally {
|
|
2723
|
-
_promiseInit = void 0;
|
|
2724
|
-
}
|
|
2725
|
-
}
|
|
2726
|
-
let { promise, resolve, reject } = createPromiseWithResolvers();
|
|
2727
|
-
const id = nanoid();
|
|
2728
|
-
req.i = id;
|
|
2729
|
-
let timeoutId;
|
|
2730
|
-
async function handler(newReq = req) {
|
|
2731
|
-
if (timeout >= 0) {
|
|
2732
|
-
timeoutId = setTimeout$1(() => {
|
|
2733
|
-
try {
|
|
2734
|
-
const handleResult = options.onTimeoutError?.(method, args);
|
|
2735
|
-
if (handleResult !== true)
|
|
2736
|
-
throw new Error(`[birpc] timeout on calling "${method}"`);
|
|
2737
|
-
} catch (e) {
|
|
2738
|
-
reject(e);
|
|
2739
|
-
}
|
|
2740
|
-
_rpcPromiseMap.delete(id);
|
|
2741
|
-
}, timeout);
|
|
2742
|
-
if (typeof timeoutId === "object")
|
|
2743
|
-
timeoutId = timeoutId.unref?.();
|
|
2744
|
-
}
|
|
2745
|
-
_rpcPromiseMap.set(id, { resolve, reject, timeoutId, method });
|
|
2746
|
-
await send(newReq);
|
|
2747
|
-
return promise;
|
|
2748
|
-
}
|
|
2749
|
-
try {
|
|
2750
|
-
if (options.onRequest)
|
|
2751
|
-
await options.onRequest(req, handler, resolve);
|
|
2752
|
-
else
|
|
2753
|
-
await handler();
|
|
2754
|
-
} catch (e) {
|
|
2755
|
-
if (options.onGeneralError?.(e) !== true)
|
|
2756
|
-
throw e;
|
|
2757
|
-
return;
|
|
2758
|
-
} finally {
|
|
2759
|
-
clearTimeout(timeoutId);
|
|
2760
|
-
_rpcPromiseMap.delete(id);
|
|
2761
|
-
}
|
|
2762
|
-
return promise;
|
|
2763
|
-
}
|
|
2764
|
-
const $call = (method, ...args) => _call(method, args, false);
|
|
2765
|
-
const $callOptional = (method, ...args) => _call(method, args, false, true);
|
|
2766
|
-
const $callEvent = (method, ...args) => _call(method, args, true);
|
|
2767
|
-
const $callRaw = (options2) => _call(options2.method, options2.args, options2.event, options2.optional);
|
|
2768
|
-
const builtinMethods = {
|
|
2769
|
-
$call,
|
|
2770
|
-
$callOptional,
|
|
2771
|
-
$callEvent,
|
|
2772
|
-
$callRaw,
|
|
2773
|
-
$rejectPendingCalls,
|
|
2774
|
-
get $closed() {
|
|
2775
|
-
return $closed;
|
|
2776
|
-
},
|
|
2777
|
-
$close,
|
|
2778
|
-
$functions
|
|
2779
|
-
};
|
|
2780
|
-
const rpc = new Proxy({}, {
|
|
2781
|
-
get(_, method) {
|
|
2782
|
-
if (Object.prototype.hasOwnProperty.call(builtinMethods, method))
|
|
2783
|
-
return builtinMethods[method];
|
|
2784
|
-
if (method === "then" && !eventNames.includes("then") && !("then" in $functions))
|
|
2785
|
-
return void 0;
|
|
2786
|
-
const sendEvent = (...args) => _call(method, args, true);
|
|
2787
|
-
if (eventNames.includes(method)) {
|
|
2788
|
-
sendEvent.asEvent = sendEvent;
|
|
2789
|
-
return sendEvent;
|
|
2790
|
-
}
|
|
2791
|
-
const sendCall = (...args) => _call(method, args, false);
|
|
2792
|
-
sendCall.asEvent = sendEvent;
|
|
2793
|
-
return sendCall;
|
|
2794
|
-
}
|
|
2795
|
-
});
|
|
2796
|
-
function $close(customError) {
|
|
2797
|
-
$closed = true;
|
|
2798
|
-
_rpcPromiseMap.forEach(({ reject, method }) => {
|
|
2799
|
-
const error = new Error(`[birpc] rpc is closed, cannot call "${method}"`);
|
|
2800
|
-
if (customError) {
|
|
2801
|
-
customError.cause ??= error;
|
|
2802
|
-
return reject(customError);
|
|
2803
|
-
}
|
|
2804
|
-
reject(error);
|
|
2805
|
-
});
|
|
2806
|
-
_rpcPromiseMap.clear();
|
|
2807
|
-
off(onMessage);
|
|
2808
|
-
}
|
|
2809
|
-
function $rejectPendingCalls(handler) {
|
|
2810
|
-
const entries = Array.from(_rpcPromiseMap.values());
|
|
2811
|
-
const handlerResults = entries.map(({ method, reject }) => {
|
|
2812
|
-
if (!handler) {
|
|
2813
|
-
return reject(new Error(`[birpc]: rejected pending call "${method}".`));
|
|
2814
|
-
}
|
|
2815
|
-
return handler({ method, reject });
|
|
2816
|
-
});
|
|
2817
|
-
_rpcPromiseMap.clear();
|
|
2818
|
-
return handlerResults;
|
|
2819
|
-
}
|
|
2820
|
-
async function onMessage(data, ...extra) {
|
|
2821
|
-
let msg;
|
|
2822
|
-
try {
|
|
2823
|
-
msg = deserialize(data);
|
|
2824
|
-
} catch (e) {
|
|
2825
|
-
if (options.onGeneralError?.(e) !== true)
|
|
2826
|
-
throw e;
|
|
2827
|
-
return;
|
|
2828
|
-
}
|
|
2829
|
-
if (msg.t === TYPE_REQUEST) {
|
|
2830
|
-
const { m: method, a: args, o: optional } = msg;
|
|
2831
|
-
let result, error;
|
|
2832
|
-
let fn = await (resolver ? resolver(method, $functions[method]) : $functions[method]);
|
|
2833
|
-
if (optional)
|
|
2834
|
-
fn ||= () => void 0;
|
|
2835
|
-
if (!fn) {
|
|
2836
|
-
error = new Error(`[birpc] function "${method}" not found`);
|
|
2837
|
-
} else {
|
|
2838
|
-
try {
|
|
2839
|
-
result = await fn.apply(bind === "rpc" ? rpc : $functions, args);
|
|
2840
|
-
} catch (e) {
|
|
2841
|
-
error = e;
|
|
2842
|
-
}
|
|
2843
|
-
}
|
|
2844
|
-
if (msg.i) {
|
|
2845
|
-
if (error && options.onError)
|
|
2846
|
-
options.onError(error, method, args);
|
|
2847
|
-
if (error && options.onFunctionError) {
|
|
2848
|
-
if (options.onFunctionError(error, method, args) === true)
|
|
2849
|
-
return;
|
|
2850
|
-
}
|
|
2851
|
-
if (!error) {
|
|
2852
|
-
try {
|
|
2853
|
-
await post(serialize({ t: TYPE_RESPONSE, i: msg.i, r: result }), ...extra);
|
|
2854
|
-
return;
|
|
2855
|
-
} catch (e) {
|
|
2856
|
-
error = e;
|
|
2857
|
-
if (options.onGeneralError?.(e, method, args) !== true)
|
|
2858
|
-
throw e;
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
try {
|
|
2862
|
-
await post(serialize({ t: TYPE_RESPONSE, i: msg.i, e: error }), ...extra);
|
|
2863
|
-
} catch (e) {
|
|
2864
|
-
if (options.onGeneralError?.(e, method, args) !== true)
|
|
2865
|
-
throw e;
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
} else {
|
|
2869
|
-
const { i: ack, r: result, e: error } = msg;
|
|
2870
|
-
const promise = _rpcPromiseMap.get(ack);
|
|
2871
|
-
if (promise) {
|
|
2872
|
-
clearTimeout(promise.timeoutId);
|
|
2873
|
-
if (error)
|
|
2874
|
-
promise.reject(error);
|
|
2875
|
-
else
|
|
2876
|
-
promise.resolve(result);
|
|
2877
|
-
}
|
|
2878
|
-
_rpcPromiseMap.delete(ack);
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
2881
|
-
_promiseInit = on(onMessage);
|
|
2882
|
-
return rpc;
|
|
2883
|
-
}
|
|
2795
|
+
|
|
2796
|
+
//#endregion
|
|
2797
|
+
//#region src/utils.ts
|
|
2884
2798
|
function createPromiseWithResolvers() {
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2799
|
+
let resolve;
|
|
2800
|
+
let reject;
|
|
2801
|
+
return {
|
|
2802
|
+
promise: new Promise((res, rej) => {
|
|
2803
|
+
resolve = res;
|
|
2804
|
+
reject = rej;
|
|
2805
|
+
}),
|
|
2806
|
+
resolve,
|
|
2807
|
+
reject
|
|
2808
|
+
};
|
|
2892
2809
|
}
|
|
2810
|
+
const random = Math.random.bind(Math);
|
|
2893
2811
|
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
2894
2812
|
function nanoid(size = 21) {
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2813
|
+
let id = "";
|
|
2814
|
+
let i = size;
|
|
2815
|
+
while (i--) id += urlAlphabet[random() * 64 | 0];
|
|
2816
|
+
return id;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
//#endregion
|
|
2820
|
+
//#region src/main.ts
|
|
2821
|
+
const DEFAULT_TIMEOUT = 6e4;
|
|
2822
|
+
const defaultSerialize = (i) => i;
|
|
2823
|
+
const defaultDeserialize = defaultSerialize;
|
|
2824
|
+
const { clearTimeout, setTimeout: setTimeout$1 } = globalThis;
|
|
2825
|
+
function createBirpc($functions, options) {
|
|
2826
|
+
const { post, on, off = () => {}, eventNames = [], serialize = defaultSerialize, deserialize = defaultDeserialize, resolver, bind = "rpc", timeout = DEFAULT_TIMEOUT, proxify = true } = options;
|
|
2827
|
+
let $closed = false;
|
|
2828
|
+
const _rpcPromiseMap = /* @__PURE__ */ new Map();
|
|
2829
|
+
let _promiseInit;
|
|
2830
|
+
let rpc;
|
|
2831
|
+
async function _call(method, args, event, optional) {
|
|
2832
|
+
if ($closed) throw new Error(`[birpc] rpc is closed, cannot call "${method}"`);
|
|
2833
|
+
const req = {
|
|
2834
|
+
m: method,
|
|
2835
|
+
a: args,
|
|
2836
|
+
t: TYPE_REQUEST
|
|
2837
|
+
};
|
|
2838
|
+
if (optional) req.o = true;
|
|
2839
|
+
const send = async (_req) => post(serialize(_req));
|
|
2840
|
+
if (event) {
|
|
2841
|
+
await send(req);
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2844
|
+
if (_promiseInit) try {
|
|
2845
|
+
await _promiseInit;
|
|
2846
|
+
} finally {
|
|
2847
|
+
_promiseInit = void 0;
|
|
2848
|
+
}
|
|
2849
|
+
let { promise, resolve, reject } = createPromiseWithResolvers();
|
|
2850
|
+
const id = nanoid();
|
|
2851
|
+
req.i = id;
|
|
2852
|
+
let timeoutId;
|
|
2853
|
+
async function handler(newReq = req) {
|
|
2854
|
+
if (timeout >= 0) {
|
|
2855
|
+
timeoutId = setTimeout$1(() => {
|
|
2856
|
+
try {
|
|
2857
|
+
if (options.onTimeoutError?.call(rpc, method, args) !== true) throw new Error(`[birpc] timeout on calling "${method}"`);
|
|
2858
|
+
} catch (e) {
|
|
2859
|
+
reject(e);
|
|
2860
|
+
}
|
|
2861
|
+
_rpcPromiseMap.delete(id);
|
|
2862
|
+
}, timeout);
|
|
2863
|
+
if (typeof timeoutId === "object") timeoutId = timeoutId.unref?.();
|
|
2864
|
+
}
|
|
2865
|
+
_rpcPromiseMap.set(id, {
|
|
2866
|
+
resolve,
|
|
2867
|
+
reject,
|
|
2868
|
+
timeoutId,
|
|
2869
|
+
method
|
|
2870
|
+
});
|
|
2871
|
+
await send(newReq);
|
|
2872
|
+
return promise;
|
|
2873
|
+
}
|
|
2874
|
+
try {
|
|
2875
|
+
if (options.onRequest) await options.onRequest.call(rpc, req, handler, resolve);
|
|
2876
|
+
else await handler();
|
|
2877
|
+
} catch (e) {
|
|
2878
|
+
if (options.onGeneralError?.call(rpc, e) !== true) throw e;
|
|
2879
|
+
return;
|
|
2880
|
+
} finally {
|
|
2881
|
+
clearTimeout(timeoutId);
|
|
2882
|
+
_rpcPromiseMap.delete(id);
|
|
2883
|
+
}
|
|
2884
|
+
return promise;
|
|
2885
|
+
}
|
|
2886
|
+
const builtinMethods = {
|
|
2887
|
+
$call: (method, ...args) => _call(method, args, false),
|
|
2888
|
+
$callOptional: (method, ...args) => _call(method, args, false, true),
|
|
2889
|
+
$callEvent: (method, ...args) => _call(method, args, true),
|
|
2890
|
+
$callRaw: (options$1) => _call(options$1.method, options$1.args, options$1.event, options$1.optional),
|
|
2891
|
+
$rejectPendingCalls,
|
|
2892
|
+
get $closed() {
|
|
2893
|
+
return $closed;
|
|
2894
|
+
},
|
|
2895
|
+
get $meta() {
|
|
2896
|
+
return options.meta;
|
|
2897
|
+
},
|
|
2898
|
+
$close,
|
|
2899
|
+
$functions
|
|
2900
|
+
};
|
|
2901
|
+
if (proxify) rpc = new Proxy({}, { get(_, method) {
|
|
2902
|
+
if (Object.prototype.hasOwnProperty.call(builtinMethods, method)) return builtinMethods[method];
|
|
2903
|
+
if (method === "then" && !eventNames.includes("then") && !("then" in $functions)) return void 0;
|
|
2904
|
+
const sendEvent = (...args) => _call(method, args, true);
|
|
2905
|
+
if (eventNames.includes(method)) {
|
|
2906
|
+
sendEvent.asEvent = sendEvent;
|
|
2907
|
+
return sendEvent;
|
|
2908
|
+
}
|
|
2909
|
+
const sendCall = (...args) => _call(method, args, false);
|
|
2910
|
+
sendCall.asEvent = sendEvent;
|
|
2911
|
+
return sendCall;
|
|
2912
|
+
} });
|
|
2913
|
+
else rpc = builtinMethods;
|
|
2914
|
+
function $close(customError) {
|
|
2915
|
+
$closed = true;
|
|
2916
|
+
_rpcPromiseMap.forEach(({ reject, method }) => {
|
|
2917
|
+
const error = /* @__PURE__ */ new Error(`[birpc] rpc is closed, cannot call "${method}"`);
|
|
2918
|
+
if (customError) {
|
|
2919
|
+
customError.cause ??= error;
|
|
2920
|
+
return reject(customError);
|
|
2921
|
+
}
|
|
2922
|
+
reject(error);
|
|
2923
|
+
});
|
|
2924
|
+
_rpcPromiseMap.clear();
|
|
2925
|
+
off(onMessage);
|
|
2926
|
+
}
|
|
2927
|
+
function $rejectPendingCalls(handler) {
|
|
2928
|
+
const handlerResults = Array.from(_rpcPromiseMap.values()).map(({ method, reject }) => {
|
|
2929
|
+
if (!handler) return reject(/* @__PURE__ */ new Error(`[birpc]: rejected pending call "${method}".`));
|
|
2930
|
+
return handler({
|
|
2931
|
+
method,
|
|
2932
|
+
reject
|
|
2933
|
+
});
|
|
2934
|
+
});
|
|
2935
|
+
_rpcPromiseMap.clear();
|
|
2936
|
+
return handlerResults;
|
|
2937
|
+
}
|
|
2938
|
+
async function onMessage(data, ...extra) {
|
|
2939
|
+
let msg;
|
|
2940
|
+
try {
|
|
2941
|
+
msg = deserialize(data);
|
|
2942
|
+
} catch (e) {
|
|
2943
|
+
if (options.onGeneralError?.call(rpc, e) !== true) throw e;
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
if (msg.t === TYPE_REQUEST) {
|
|
2947
|
+
const { m: method, a: args, o: optional } = msg;
|
|
2948
|
+
let result, error;
|
|
2949
|
+
let fn = await (resolver ? resolver.call(rpc, method, $functions[method]) : $functions[method]);
|
|
2950
|
+
if (optional) fn ||= () => void 0;
|
|
2951
|
+
if (!fn) error = /* @__PURE__ */ new Error(`[birpc] function "${method}" not found`);
|
|
2952
|
+
else try {
|
|
2953
|
+
result = await fn.apply(bind === "rpc" ? rpc : $functions, args);
|
|
2954
|
+
} catch (e) {
|
|
2955
|
+
error = e;
|
|
2956
|
+
}
|
|
2957
|
+
if (msg.i) {
|
|
2958
|
+
if (error && options.onFunctionError) {
|
|
2959
|
+
if (options.onFunctionError.call(rpc, error, method, args) === true) return;
|
|
2960
|
+
}
|
|
2961
|
+
if (!error) try {
|
|
2962
|
+
await post(serialize({
|
|
2963
|
+
t: TYPE_RESPONSE,
|
|
2964
|
+
i: msg.i,
|
|
2965
|
+
r: result
|
|
2966
|
+
}), ...extra);
|
|
2967
|
+
return;
|
|
2968
|
+
} catch (e) {
|
|
2969
|
+
error = e;
|
|
2970
|
+
if (options.onGeneralError?.call(rpc, e, method, args) !== true) throw e;
|
|
2971
|
+
}
|
|
2972
|
+
try {
|
|
2973
|
+
await post(serialize({
|
|
2974
|
+
t: TYPE_RESPONSE,
|
|
2975
|
+
i: msg.i,
|
|
2976
|
+
e: error
|
|
2977
|
+
}), ...extra);
|
|
2978
|
+
} catch (e) {
|
|
2979
|
+
if (options.onGeneralError?.call(rpc, e, method, args) !== true) throw e;
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
} else {
|
|
2983
|
+
const { i: ack, r: result, e: error } = msg;
|
|
2984
|
+
const promise = _rpcPromiseMap.get(ack);
|
|
2985
|
+
if (promise) {
|
|
2986
|
+
clearTimeout(promise.timeoutId);
|
|
2987
|
+
if (error) promise.reject(error);
|
|
2988
|
+
else promise.resolve(result);
|
|
2989
|
+
}
|
|
2990
|
+
_rpcPromiseMap.delete(ack);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
_promiseInit = on(onMessage);
|
|
2994
|
+
return rpc;
|
|
2900
2995
|
}
|
|
2901
2996
|
|
|
2902
2997
|
const debug = createDebugger("vitest:browser:api");
|
|
@@ -3246,6 +3341,7 @@ const createBrowserServer = async (options) => {
|
|
|
3246
3341
|
let cacheDir;
|
|
3247
3342
|
const vite = await createViteServer({
|
|
3248
3343
|
...project.options,
|
|
3344
|
+
define: project.config.viteDefine,
|
|
3249
3345
|
base: "/",
|
|
3250
3346
|
root: project.config.root,
|
|
3251
3347
|
logLevel,
|