@crvy/rprtr 0.3.2 → 0.3.3
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/CHANGELOG.md +19 -0
- package/README.md +43 -1
- package/dist/chunk-7JUICMVX.js +62 -0
- package/dist/{chunk-C5SVPK2O.js → chunk-NXW25MU3.js} +100 -44
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +30 -10
- package/dist/fontconfig.d.ts +43 -0
- package/dist/fontconfig.d.ts.map +1 -0
- package/dist/index.css +4 -0
- package/dist/rendering.cjs +114 -0
- package/dist/rendering.d.ts +61 -0
- package/dist/rendering.d.ts.map +1 -0
- package/dist/rendering.js +30 -0
- package/dist/server/app.d.ts +9 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/docker-env.d.ts +7 -0
- package/dist/server/docker-env.d.ts.map +1 -0
- package/dist/server/docker-launcher.d.ts +11 -0
- package/dist/server/docker-launcher.d.ts.map +1 -1
- package/dist/server/fontconfig.d.ts +22 -0
- package/dist/server/fontconfig.d.ts.map +1 -0
- package/dist/server/launcher-resolver.d.ts +6 -0
- package/dist/server/launcher-resolver.d.ts.map +1 -1
- package/dist/server/run-launcher.d.ts +13 -2
- package/dist/server/run-launcher.d.ts.map +1 -1
- package/dist/server.cjs +181 -69
- package/dist/server.js +2 -1
- package/package.json +10 -3
package/dist/server.cjs
CHANGED
|
@@ -35,7 +35,7 @@ __export(server_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(server_exports);
|
|
36
36
|
|
|
37
37
|
// src/server/app.ts
|
|
38
|
-
var
|
|
38
|
+
var import_path11 = require("path");
|
|
39
39
|
var import_url = require("url");
|
|
40
40
|
|
|
41
41
|
// src/offline-reports.ts
|
|
@@ -659,6 +659,47 @@ function mergeOfflineReportsIntoTests(existingTests, offlineReports, options = {
|
|
|
659
659
|
};
|
|
660
660
|
}
|
|
661
661
|
|
|
662
|
+
// src/server/docker-env.ts
|
|
663
|
+
var ENV_DENYLIST = /* @__PURE__ */ new Set([
|
|
664
|
+
"CI",
|
|
665
|
+
"PLAYWRIGHT_BROWSERS_PATH",
|
|
666
|
+
"CRVY_RPRTR_SERVER_URL",
|
|
667
|
+
"CRVY_RPRTR_PORTABLE_ARTIFACTS",
|
|
668
|
+
"TZ",
|
|
669
|
+
// The container must use its own home: a forwarded host HOME makes NSS (browser cert
|
|
670
|
+
// store init) and npm resolve their state under it, and a Windows-style value like
|
|
671
|
+
// `C:\Users\dev` is a *relative* path on Linux, so those tools would create a literal
|
|
672
|
+
// `C:\Users\dev/.local/share/pki/nssdb` tree under the container CWD — i.e. inside the
|
|
673
|
+
// bind-mounted project.
|
|
674
|
+
"HOME",
|
|
675
|
+
"NPM_CONFIG_CACHE",
|
|
676
|
+
"LANG",
|
|
677
|
+
"LC_ALL",
|
|
678
|
+
"PLAYWRIGHT_HTML_OPEN",
|
|
679
|
+
"PATH",
|
|
680
|
+
// Host paths that do not exist in the container: fontconfig resolves them to nothing and
|
|
681
|
+
// the browser ends up with no font directories at all (blank text). The container gets its
|
|
682
|
+
// AA from the mounted drop-in instead.
|
|
683
|
+
"FONTCONFIG_FILE",
|
|
684
|
+
"FONTCONFIG_PATH"
|
|
685
|
+
]);
|
|
686
|
+
var WINDOWS_ENV_NOISE = new Set(
|
|
687
|
+
"SYSTEMROOT COMSPEC WINDIR PATHEXT OS PROGRAMFILES PROGRAMFILES(X86) PROGRAMW6432 PROGRAMDATA ALLUSERSPROFILE PUBLIC APPDATA LOCALAPPDATA TEMP TMP USERPROFILE HOMEDRIVE HOMEPATH USERNAME PSMODULEPATH DRIVERDATA NUMBER_OF_PROCESSORS PROCESSOR_ARCHITECTURE PROCESSOR_LEVEL PROCESSOR_REVISION".split(
|
|
688
|
+
" "
|
|
689
|
+
)
|
|
690
|
+
);
|
|
691
|
+
var WINDOWS_PATH_VALUE = /^(?:[A-Za-z]:[\\/]|\\\\)/;
|
|
692
|
+
function collectForwardedEnvNames(env, platform) {
|
|
693
|
+
const names = [];
|
|
694
|
+
for (const [key, value] of Object.entries(env)) {
|
|
695
|
+
const upper = key.toUpperCase();
|
|
696
|
+
if (ENV_DENYLIST.has(upper) || WINDOWS_ENV_NOISE.has(upper) || value === void 0) continue;
|
|
697
|
+
if (platform === "win32" && WINDOWS_PATH_VALUE.test(value)) continue;
|
|
698
|
+
names.push(key);
|
|
699
|
+
}
|
|
700
|
+
return names;
|
|
701
|
+
}
|
|
702
|
+
|
|
662
703
|
// src/server/docker-support.ts
|
|
663
704
|
var import_child_process = require("child_process");
|
|
664
705
|
var import_node_fs = require("node:fs");
|
|
@@ -822,6 +863,81 @@ function buildTestListEntries(tests, rootDir, cwd, pathStyle = "host") {
|
|
|
822
863
|
});
|
|
823
864
|
}
|
|
824
865
|
|
|
866
|
+
// src/server/fontconfig.ts
|
|
867
|
+
var import_fs2 = require("fs");
|
|
868
|
+
var import_os2 = require("os");
|
|
869
|
+
var import_path4 = require("path");
|
|
870
|
+
|
|
871
|
+
// src/fontconfig.ts
|
|
872
|
+
var import_fs = require("fs");
|
|
873
|
+
var import_os = require("os");
|
|
874
|
+
var import_path3 = require("path");
|
|
875
|
+
var GRAYSCALE_MATCH = ` <match target="font">
|
|
876
|
+
<edit name="rgba" mode="assign"><const>none</const></edit>
|
|
877
|
+
</match>`;
|
|
878
|
+
var HEADER = `<?xml version="1.0"?>
|
|
879
|
+
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
|
|
880
|
+
<!-- Written by crvy-rprtr: pins grayscale text antialiasing for screenshot determinism. -->`;
|
|
881
|
+
var GRAYSCALE_FONTCONFIG_XML = `${HEADER}
|
|
882
|
+
<fontconfig>
|
|
883
|
+
${GRAYSCALE_MATCH}
|
|
884
|
+
</fontconfig>
|
|
885
|
+
`;
|
|
886
|
+
function grayscaleRootFontconfigXml(systemConfigPath) {
|
|
887
|
+
return `${HEADER}
|
|
888
|
+
<fontconfig>
|
|
889
|
+
<include ignore_missing="no">${escapeXml(systemConfigPath)}</include>
|
|
890
|
+
${GRAYSCALE_MATCH}
|
|
891
|
+
</fontconfig>
|
|
892
|
+
`;
|
|
893
|
+
}
|
|
894
|
+
function escapeXml(value) {
|
|
895
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
896
|
+
}
|
|
897
|
+
var SYSTEM_FONTCONFIG_PATHS = ["/etc/fonts/fonts.conf", "/usr/local/etc/fonts/fonts.conf"];
|
|
898
|
+
function rootFontconfigPath() {
|
|
899
|
+
return (0, import_path3.join)((0, import_os.tmpdir)(), "crvy-rprtr", "fonts.conf");
|
|
900
|
+
}
|
|
901
|
+
function resolveSystemFontconfig(env = process.env, exists = import_fs.existsSync) {
|
|
902
|
+
const ours = rootFontconfigPath();
|
|
903
|
+
const candidates = [
|
|
904
|
+
env.FONTCONFIG_FILE,
|
|
905
|
+
env.FONTCONFIG_PATH === void 0 ? void 0 : (0, import_path3.join)(env.FONTCONFIG_PATH, "fonts.conf"),
|
|
906
|
+
...SYSTEM_FONTCONFIG_PATHS
|
|
907
|
+
];
|
|
908
|
+
for (const candidate of candidates) {
|
|
909
|
+
if (candidate === void 0 || candidate === ours) continue;
|
|
910
|
+
if (exists(candidate)) return candidate;
|
|
911
|
+
}
|
|
912
|
+
return null;
|
|
913
|
+
}
|
|
914
|
+
function ensureRootFontconfig(systemConfigPath) {
|
|
915
|
+
const path = rootFontconfigPath();
|
|
916
|
+
(0, import_fs.mkdirSync)((0, import_path3.join)((0, import_os.tmpdir)(), "crvy-rprtr"), { recursive: true });
|
|
917
|
+
const staging = `${path}.${process.pid}.tmp`;
|
|
918
|
+
(0, import_fs.writeFileSync)(staging, grayscaleRootFontconfigXml(systemConfigPath), "utf8");
|
|
919
|
+
(0, import_fs.renameSync)(staging, path);
|
|
920
|
+
return path;
|
|
921
|
+
}
|
|
922
|
+
function grayscaleFontconfigEnv(baseEnv = process.env, options = {}) {
|
|
923
|
+
if ((options.platform ?? process.platform) !== "linux") return null;
|
|
924
|
+
const systemConfig = resolveSystemFontconfig(baseEnv, options.exists);
|
|
925
|
+
if (systemConfig === null) return null;
|
|
926
|
+
return { FONTCONFIG_FILE: (options.writeConfig ?? ensureRootFontconfig)(systemConfig) };
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/server/fontconfig.ts
|
|
930
|
+
var CONTAINER_FONTCONFIG_PATH = "/etc/fonts/conf.d/99-crvy-rprtr-grayscale.conf";
|
|
931
|
+
function hostFontconfigPath() {
|
|
932
|
+
return (0, import_path4.join)((0, import_os2.tmpdir)(), "crvy-rprtr", "99-crvy-rprtr-grayscale.conf");
|
|
933
|
+
}
|
|
934
|
+
function ensureGrayscaleFontconfig() {
|
|
935
|
+
const path = hostFontconfigPath();
|
|
936
|
+
(0, import_fs2.mkdirSync)((0, import_path4.join)((0, import_os2.tmpdir)(), "crvy-rprtr"), { recursive: true });
|
|
937
|
+
(0, import_fs2.writeFileSync)(path, GRAYSCALE_FONTCONFIG_XML, "utf8");
|
|
938
|
+
return path;
|
|
939
|
+
}
|
|
940
|
+
|
|
825
941
|
// src/server/run-controller.ts
|
|
826
942
|
var import_child_process2 = require("child_process");
|
|
827
943
|
var import_node_fs2 = require("node:fs");
|
|
@@ -838,7 +954,7 @@ function resolvePlaywrightLaunch(cwd, playwrightArgs) {
|
|
|
838
954
|
if (resolved !== null) return { cmd: resolved.command, args: resolved.args };
|
|
839
955
|
return { cmd: "npx", args: ["playwright", ...playwrightArgs] };
|
|
840
956
|
}
|
|
841
|
-
function buildSpawnEnv(port, baseEnv = process.env) {
|
|
957
|
+
function buildSpawnEnv(port, baseEnv = process.env, options = {}) {
|
|
842
958
|
const env = {};
|
|
843
959
|
for (const [key, value] of Object.entries(baseEnv)) {
|
|
844
960
|
if (key === "CI") continue;
|
|
@@ -846,6 +962,10 @@ function buildSpawnEnv(port, baseEnv = process.env) {
|
|
|
846
962
|
}
|
|
847
963
|
env.CRVY_RPRTR_SERVER_URL = `ws://localhost:${port}`;
|
|
848
964
|
env.PLAYWRIGHT_HTML_OPEN = "never";
|
|
965
|
+
const { fontRendering = "grayscale", ...fontconfigOptions } = options;
|
|
966
|
+
if (fontRendering !== "inherit") {
|
|
967
|
+
Object.assign(env, grayscaleFontconfigEnv(baseEnv, fontconfigOptions) ?? {});
|
|
968
|
+
}
|
|
849
969
|
return env;
|
|
850
970
|
}
|
|
851
971
|
function createLocalLauncher(options) {
|
|
@@ -854,7 +974,8 @@ function createLocalLauncher(options) {
|
|
|
854
974
|
launch({ ctx, playwrightArgs }) {
|
|
855
975
|
const resolve6 = options.resolveLaunch ?? resolvePlaywrightLaunch;
|
|
856
976
|
const { cmd, args } = resolve6(ctx.cwd, playwrightArgs);
|
|
857
|
-
|
|
977
|
+
const { port, resolveLaunch: _resolveLaunch, env: baseEnv, ...spawnEnvOptions } = options;
|
|
978
|
+
return { cmd, args, env: buildSpawnEnv(port, baseEnv, spawnEnvOptions) };
|
|
858
979
|
}
|
|
859
980
|
};
|
|
860
981
|
}
|
|
@@ -1061,22 +1182,6 @@ function createRealTimers() {
|
|
|
1061
1182
|
// src/server/docker-launcher.ts
|
|
1062
1183
|
var DOCKER_WORK_DIR = "/work";
|
|
1063
1184
|
var DOCKER_HOST_GATEWAY = "host.docker.internal";
|
|
1064
|
-
var ENV_DENYLIST = /* @__PURE__ */ new Set([
|
|
1065
|
-
"CI",
|
|
1066
|
-
"PLAYWRIGHT_BROWSERS_PATH",
|
|
1067
|
-
"CRVY_RPRTR_SERVER_URL",
|
|
1068
|
-
"CRVY_RPRTR_PORTABLE_ARTIFACTS",
|
|
1069
|
-
"TZ",
|
|
1070
|
-
"LANG",
|
|
1071
|
-
"LC_ALL",
|
|
1072
|
-
"PLAYWRIGHT_HTML_OPEN",
|
|
1073
|
-
"PATH"
|
|
1074
|
-
]);
|
|
1075
|
-
var WINDOWS_ENV_NOISE = new Set(
|
|
1076
|
-
"SYSTEMROOT COMSPEC WINDIR PATHEXT OS PROGRAMFILES PROGRAMFILES(X86) PROGRAMW6432 PROGRAMDATA ALLUSERSPROFILE PUBLIC APPDATA LOCALAPPDATA TEMP TMP USERPROFILE HOMEDRIVE HOMEPATH USERNAME PSMODULEPATH DRIVERDATA NUMBER_OF_PROCESSORS PROCESSOR_ARCHITECTURE PROCESSOR_IDENTIFIER PROCESSOR_LEVEL PROCESSOR_REVISION".split(
|
|
1077
|
-
" "
|
|
1078
|
-
)
|
|
1079
|
-
);
|
|
1080
1185
|
var DockerUnavailableError = class extends Error {
|
|
1081
1186
|
constructor() {
|
|
1082
1187
|
super("Docker daemon is not available");
|
|
@@ -1142,9 +1247,10 @@ function buildDockerRunArgs(ctx, playwrightArgs, deps) {
|
|
|
1142
1247
|
args.push("-e", `CRVY_RPRTR_SERVER_URL=ws://${DOCKER_HOST_GATEWAY}:${deps.port}`);
|
|
1143
1248
|
args.push("-e", "CRVY_RPRTR_PORTABLE_ARTIFACTS=1", "-e", "TZ=UTC", "-e", "LANG=C.UTF-8", "-e", "LC_ALL=C.UTF-8");
|
|
1144
1249
|
args.push("-e", "PLAYWRIGHT_HTML_OPEN=never");
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1250
|
+
if (deps.docker?.fontRendering !== "inherit") {
|
|
1251
|
+
args.push("-v", `${ensureGrayscaleFontconfig()}:${CONTAINER_FONTCONFIG_PATH}:ro`);
|
|
1252
|
+
}
|
|
1253
|
+
for (const key of collectForwardedEnvNames(deps.env, deps.platform)) {
|
|
1148
1254
|
args.push("-e", key);
|
|
1149
1255
|
}
|
|
1150
1256
|
if (deps.docker?.extraArgs !== void 0) args.push(...deps.docker.extraArgs);
|
|
@@ -1207,7 +1313,8 @@ function buildLauncher(state, deps) {
|
|
|
1207
1313
|
env: deps.baseEnv,
|
|
1208
1314
|
image,
|
|
1209
1315
|
command: state.command,
|
|
1210
|
-
warn: deps.warn
|
|
1316
|
+
warn: deps.warn,
|
|
1317
|
+
platform: deps.platform
|
|
1211
1318
|
});
|
|
1212
1319
|
return { cmd: "docker", args, env: stripCi(deps.baseEnv) };
|
|
1213
1320
|
},
|
|
@@ -1232,17 +1339,17 @@ function createDockerLauncher(options) {
|
|
|
1232
1339
|
}
|
|
1233
1340
|
|
|
1234
1341
|
// src/server/handlers.ts
|
|
1235
|
-
var
|
|
1236
|
-
var
|
|
1342
|
+
var import_fs4 = require("fs");
|
|
1343
|
+
var import_path8 = require("path");
|
|
1237
1344
|
|
|
1238
1345
|
// src/server/artifact-routes.ts
|
|
1239
|
-
var
|
|
1346
|
+
var import_fs3 = require("fs");
|
|
1240
1347
|
var import_promises3 = require("fs/promises");
|
|
1241
|
-
var
|
|
1348
|
+
var import_path7 = require("path");
|
|
1242
1349
|
|
|
1243
1350
|
// src/snapshot-path-resolver.ts
|
|
1244
1351
|
var import_crypto = require("crypto");
|
|
1245
|
-
var
|
|
1352
|
+
var import_path5 = require("path");
|
|
1246
1353
|
var DEFAULT_SCREENSHOT_TEMPLATE = "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}";
|
|
1247
1354
|
var WINDOWS_FILESYSTEM_FRIENDLY_LENGTH = 60;
|
|
1248
1355
|
var PLAYWRIGHT_SNAPSHOT_NAME_LIMIT = 100;
|
|
@@ -1278,16 +1385,16 @@ function trimLongString(value, length = WINDOWS_FILESYSTEM_FRIENDLY_LENGTH) {
|
|
|
1278
1385
|
const end = length - middle.length - start;
|
|
1279
1386
|
return value.slice(0, start) + middle + value.slice(-end);
|
|
1280
1387
|
}
|
|
1281
|
-
function sanitizeFilePathBeforeExtension(filePath, extension = (0,
|
|
1388
|
+
function sanitizeFilePathBeforeExtension(filePath, extension = (0, import_path5.extname)(filePath)) {
|
|
1282
1389
|
const base = filePath.slice(0, filePath.length - extension.length);
|
|
1283
1390
|
return sanitizeForFilePath(base) + extension;
|
|
1284
1391
|
}
|
|
1285
1392
|
function addSuffixToFilePath(filePath, suffix) {
|
|
1286
|
-
const extension = (0,
|
|
1393
|
+
const extension = (0, import_path5.extname)(filePath);
|
|
1287
1394
|
return filePath.slice(0, filePath.length - extension.length) + suffix + extension;
|
|
1288
1395
|
}
|
|
1289
1396
|
function normalizedSnapshotDir(config) {
|
|
1290
|
-
return (0,
|
|
1397
|
+
return (0, import_path5.resolve)(config.configDir, config.snapshotDir);
|
|
1291
1398
|
}
|
|
1292
1399
|
function templateValue(template, token, value) {
|
|
1293
1400
|
return template.replace(
|
|
@@ -1297,8 +1404,8 @@ function templateValue(template, token, value) {
|
|
|
1297
1404
|
}
|
|
1298
1405
|
function applyTemplate(input, nameArgument, extension) {
|
|
1299
1406
|
const template = input.config.toHaveScreenshotPathTemplate ?? input.config.snapshotPathTemplate ?? DEFAULT_SCREENSHOT_TEMPLATE;
|
|
1300
|
-
const relativeTestFilePath = (0,
|
|
1301
|
-
const parsed = (0,
|
|
1407
|
+
const relativeTestFilePath = (0, import_path5.relative)(input.config.testDir, input.testFile);
|
|
1408
|
+
const parsed = (0, import_path5.parse)(relativeTestFilePath);
|
|
1302
1409
|
const tokens = [
|
|
1303
1410
|
["testDir", input.config.testDir],
|
|
1304
1411
|
["snapshotDir", normalizedSnapshotDir(input.config)],
|
|
@@ -1316,16 +1423,16 @@ function applyTemplate(input, nameArgument, extension) {
|
|
|
1316
1423
|
(currentTemplate, [token, value]) => templateValue(currentTemplate, token, value),
|
|
1317
1424
|
template
|
|
1318
1425
|
);
|
|
1319
|
-
return (0,
|
|
1426
|
+
return (0, import_path5.resolve)(input.config.configDir, snapshotPath);
|
|
1320
1427
|
}
|
|
1321
|
-
function removeExtension(filePath, extension = (0,
|
|
1428
|
+
function removeExtension(filePath, extension = (0, import_path5.extname)(filePath)) {
|
|
1322
1429
|
return filePath.slice(0, filePath.length - extension.length);
|
|
1323
1430
|
}
|
|
1324
1431
|
function snapshotNameParts(declaredName) {
|
|
1325
|
-
const extension = (0,
|
|
1432
|
+
const extension = (0, import_path5.extname)(declaredName) || ".png";
|
|
1326
1433
|
return {
|
|
1327
1434
|
extension,
|
|
1328
|
-
filePath: (0,
|
|
1435
|
+
filePath: (0, import_path5.extname)(declaredName) === "" ? `${declaredName}${extension}` : declaredName
|
|
1329
1436
|
};
|
|
1330
1437
|
}
|
|
1331
1438
|
function filePathForOccurrence(filePath, occurrenceIndex) {
|
|
@@ -1349,7 +1456,7 @@ function resolveStringCallTarget(input, declaration) {
|
|
|
1349
1456
|
function resolveArrayCallTarget(input, declaration) {
|
|
1350
1457
|
const { extension, filePath } = snapshotNameParts(declaration.declaredName);
|
|
1351
1458
|
const occurrenceFilePath = filePathForOccurrence(filePath, declaration.occurrenceIndex);
|
|
1352
|
-
const nameArgument = (0,
|
|
1459
|
+
const nameArgument = (0, import_path5.join)((0, import_path5.dirname)(occurrenceFilePath), (0, import_path5.basename)(occurrenceFilePath, extension));
|
|
1353
1460
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
1354
1461
|
}
|
|
1355
1462
|
function resolveNamedTarget(input, declaration) {
|
|
@@ -1391,7 +1498,7 @@ function resolveTarget(input, declaration) {
|
|
|
1391
1498
|
case "unnamed": {
|
|
1392
1499
|
const anonymousFileName = anonymousName(input.reporterTitlePath, declaration.occurrenceIndex);
|
|
1393
1500
|
const extension = ".png";
|
|
1394
|
-
const nameArgument = (0,
|
|
1501
|
+
const nameArgument = (0, import_path5.join)((0, import_path5.dirname)(anonymousFileName), (0, import_path5.basename)(anonymousFileName, extension));
|
|
1395
1502
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
1396
1503
|
}
|
|
1397
1504
|
}
|
|
@@ -1404,7 +1511,7 @@ function resolveBaselineTargets(input) {
|
|
|
1404
1511
|
}
|
|
1405
1512
|
|
|
1406
1513
|
// src/server/utils.ts
|
|
1407
|
-
var
|
|
1514
|
+
var import_path6 = require("path");
|
|
1408
1515
|
var LIVE_UPDATES_WEBSOCKET_PATH = "/";
|
|
1409
1516
|
function broadcastToBrowsers(wsClients, msg) {
|
|
1410
1517
|
const payload = JSON.stringify(msg);
|
|
@@ -1416,10 +1523,10 @@ function isWebSocketUpgradeRequest(req) {
|
|
|
1416
1523
|
return new URL(req.url).pathname === LIVE_UPDATES_WEBSOCKET_PATH && req.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
1417
1524
|
}
|
|
1418
1525
|
function isPathWithinRoots(target, roots) {
|
|
1419
|
-
const resolvedTarget = (0,
|
|
1526
|
+
const resolvedTarget = (0, import_path6.resolve)(target);
|
|
1420
1527
|
return roots.some((root) => {
|
|
1421
|
-
const rel = (0,
|
|
1422
|
-
return rel === "" || !rel.startsWith(`..${
|
|
1528
|
+
const rel = (0, import_path6.relative)((0, import_path6.resolve)(root), resolvedTarget);
|
|
1529
|
+
return rel === "" || !rel.startsWith(`..${import_path6.sep}`) && rel !== ".." && !(0, import_path6.isAbsolute)(rel);
|
|
1423
1530
|
});
|
|
1424
1531
|
}
|
|
1425
1532
|
|
|
@@ -1437,7 +1544,7 @@ async function handleFile(ctx, req) {
|
|
|
1437
1544
|
let decodedPath;
|
|
1438
1545
|
try {
|
|
1439
1546
|
rawDecoded = decodeURIComponent(new URL(req.url).pathname.slice("/file/".length));
|
|
1440
|
-
decodedPath = (0,
|
|
1547
|
+
decodedPath = (0, import_path7.resolve)(rawDecoded);
|
|
1441
1548
|
} catch {
|
|
1442
1549
|
return notFound();
|
|
1443
1550
|
}
|
|
@@ -1450,7 +1557,7 @@ async function handleFile(ctx, req) {
|
|
|
1450
1557
|
}
|
|
1451
1558
|
return notFound();
|
|
1452
1559
|
}
|
|
1453
|
-
const realRoots = (await Promise.all((ctx.artifactRoots ?? []).map((root) => realpathOrNull((0,
|
|
1560
|
+
const realRoots = (await Promise.all((ctx.artifactRoots ?? []).map((root) => realpathOrNull((0, import_path7.resolve)(root))))).filter(
|
|
1454
1561
|
(root) => root !== null
|
|
1455
1562
|
);
|
|
1456
1563
|
if (!isPathWithinRoots(realTarget, realRoots)) {
|
|
@@ -1483,14 +1590,14 @@ function resolveBaselineSnapshotPath(routing, test, retry, imageName) {
|
|
|
1483
1590
|
declarations: [declaration],
|
|
1484
1591
|
config: {
|
|
1485
1592
|
configDir: routing.configDir,
|
|
1486
|
-
testDir: routing.playwrightTestDir ?? (0,
|
|
1487
|
-
snapshotDir: routing.playwrightSnapshotDir ?? (0,
|
|
1593
|
+
testDir: routing.playwrightTestDir ?? (0, import_path7.dirname)(testFile),
|
|
1594
|
+
snapshotDir: routing.playwrightSnapshotDir ?? (0, import_path7.dirname)(testFile),
|
|
1488
1595
|
projectName: test.projectName ?? test.browser,
|
|
1489
1596
|
snapshotSuffix: isContainerPath ? "linux" : process.platform,
|
|
1490
1597
|
snapshotPathTemplate: routing.playwrightSnapshotPathTemplate,
|
|
1491
1598
|
toHaveScreenshotPathTemplate: routing.playwrightToHaveScreenshotPathTemplate
|
|
1492
1599
|
},
|
|
1493
|
-
snapshotPathExists:
|
|
1600
|
+
snapshotPathExists: import_fs3.existsSync
|
|
1494
1601
|
});
|
|
1495
1602
|
return targets.length === 1 ? targets[0]?.snapshotPath ?? null : null;
|
|
1496
1603
|
}
|
|
@@ -1556,7 +1663,7 @@ function enrichDeclaredBaselines(ctx, test) {
|
|
|
1556
1663
|
continue;
|
|
1557
1664
|
}
|
|
1558
1665
|
const snapshotPath = resolveBaselineSnapshotPath(ctx.approvalRouting, test, retry, visualName);
|
|
1559
|
-
if (snapshotPath === null || !(0,
|
|
1666
|
+
if (snapshotPath === null || !(0, import_fs4.existsSync)(snapshotPath)) {
|
|
1560
1667
|
continue;
|
|
1561
1668
|
}
|
|
1562
1669
|
image.expect = `/baseline/${encodeURIComponent(test.id)}/${retry}/${encodeURIComponent(visualName)}`;
|
|
@@ -1661,11 +1768,11 @@ function handleRegister(ctx, rawData) {
|
|
|
1661
1768
|
});
|
|
1662
1769
|
}
|
|
1663
1770
|
function buildRunContext(configFile, data) {
|
|
1664
|
-
const configDir = (0,
|
|
1771
|
+
const configDir = (0, import_path8.dirname)(configFile);
|
|
1665
1772
|
return {
|
|
1666
1773
|
configFile,
|
|
1667
1774
|
cwd: configDir,
|
|
1668
|
-
rootDir: data.playwrightRootDir ?? (data.playwrightTestDir === void 0 ? configDir : (0,
|
|
1775
|
+
rootDir: data.playwrightRootDir ?? (data.playwrightTestDir === void 0 ? configDir : (0, import_path8.resolve)(configDir, data.playwrightTestDir))
|
|
1669
1776
|
};
|
|
1670
1777
|
}
|
|
1671
1778
|
|
|
@@ -1698,7 +1805,11 @@ async function resolveRunBackend(options) {
|
|
|
1698
1805
|
console.warn(`[crvy-rprtr] ${message}`);
|
|
1699
1806
|
}
|
|
1700
1807
|
});
|
|
1701
|
-
const launcher = resolvedRunMode === "docker" ? createDockerLauncher({
|
|
1808
|
+
const launcher = resolvedRunMode === "docker" ? createDockerLauncher({
|
|
1809
|
+
port: options.port,
|
|
1810
|
+
docker: { fontRendering: options.fontRendering, ...options.docker },
|
|
1811
|
+
exec: dockerExec
|
|
1812
|
+
}) : createLocalLauncher({ port: options.port, fontRendering: options.fontRendering });
|
|
1702
1813
|
return {
|
|
1703
1814
|
launcher,
|
|
1704
1815
|
routesContextOptions: {
|
|
@@ -1709,7 +1820,7 @@ async function resolveRunBackend(options) {
|
|
|
1709
1820
|
}
|
|
1710
1821
|
|
|
1711
1822
|
// src/server/playwright-config.ts
|
|
1712
|
-
var
|
|
1823
|
+
var import_path9 = require("path");
|
|
1713
1824
|
var CONFIG_FILES = [
|
|
1714
1825
|
"playwright.config.ts",
|
|
1715
1826
|
"playwright.config.mts",
|
|
@@ -1721,14 +1832,14 @@ var CONFIG_FILES = [
|
|
|
1721
1832
|
async function resolvePlaywrightConfig(cwd) {
|
|
1722
1833
|
const matches = await Promise.all(
|
|
1723
1834
|
CONFIG_FILES.map(async (file) => {
|
|
1724
|
-
const candidate = (0,
|
|
1835
|
+
const candidate = (0, import_path9.join)(cwd, file);
|
|
1725
1836
|
return await fileExists(candidate) ? candidate : null;
|
|
1726
1837
|
})
|
|
1727
1838
|
);
|
|
1728
1839
|
return matches.find((path) => path !== null) ?? null;
|
|
1729
1840
|
}
|
|
1730
1841
|
function resolveSeedConfigFile(option, cwd) {
|
|
1731
|
-
return option === void 0 ? resolvePlaywrightConfig(cwd) : Promise.resolve((0,
|
|
1842
|
+
return option === void 0 ? resolvePlaywrightConfig(cwd) : Promise.resolve((0, import_path9.resolve)(cwd, option));
|
|
1732
1843
|
}
|
|
1733
1844
|
|
|
1734
1845
|
// src/server/report-persistence.ts
|
|
@@ -1811,7 +1922,7 @@ function createRoutesContext(reportData, staticDir, saveReport, options) {
|
|
|
1811
1922
|
}
|
|
1812
1923
|
|
|
1813
1924
|
// src/server/routes.ts
|
|
1814
|
-
var
|
|
1925
|
+
var import_path10 = require("path");
|
|
1815
1926
|
|
|
1816
1927
|
// src/server/run-routes.ts
|
|
1817
1928
|
function handleRunRoutes(pathname, method, runController, req) {
|
|
@@ -1850,7 +1961,7 @@ function handleApiStop(runController) {
|
|
|
1850
1961
|
|
|
1851
1962
|
// src/server/routes.ts
|
|
1852
1963
|
async function handleRoot(ctx) {
|
|
1853
|
-
const html = await respondWithFile((0,
|
|
1964
|
+
const html = await respondWithFile((0, import_path10.join)(ctx.staticDir, "index.html"), "text/html");
|
|
1854
1965
|
return html ?? new Response("Not Found", { status: 404 });
|
|
1855
1966
|
}
|
|
1856
1967
|
async function handleAppCss() {
|
|
@@ -1874,7 +1985,7 @@ function handleApiReport(ctx) {
|
|
|
1874
1985
|
var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
|
|
1875
1986
|
function actualPathFromUrl(ctx, actualUrl) {
|
|
1876
1987
|
if (actualUrl.startsWith("/screenshots/")) {
|
|
1877
|
-
return (0,
|
|
1988
|
+
return (0, import_path10.join)(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length));
|
|
1878
1989
|
}
|
|
1879
1990
|
if (actualUrl.startsWith("/file/")) {
|
|
1880
1991
|
return decodeURIComponent(actualUrl.slice("/file/".length));
|
|
@@ -1996,7 +2107,7 @@ async function handleScreenshots(ctx, req) {
|
|
|
1996
2107
|
}
|
|
1997
2108
|
async function handleDist(ctx, req) {
|
|
1998
2109
|
const path = new URL(req.url).pathname.slice("/dist/".length);
|
|
1999
|
-
const filePath = (0,
|
|
2110
|
+
const filePath = (0, import_path10.join)(ctx.staticDir, path);
|
|
2000
2111
|
const contentType = filePath.endsWith(".css") ? "text/css" : filePath.endsWith(".js") ? "application/javascript" : filePath.endsWith(".svelte") ? "text/plain" : "application/octet-stream";
|
|
2001
2112
|
const file = await respondWithFile(filePath, contentType);
|
|
2002
2113
|
return file ?? new Response("Not Found", { status: 404 });
|
|
@@ -2183,19 +2294,19 @@ function createWebSocketMessageHandler(getHandlerContext) {
|
|
|
2183
2294
|
};
|
|
2184
2295
|
}
|
|
2185
2296
|
async function resolveStaticDir(staticDir) {
|
|
2186
|
-
const currentDir = (0,
|
|
2297
|
+
const currentDir = (0, import_path11.dirname)((0, import_url.fileURLToPath)(import_meta2.url));
|
|
2187
2298
|
const candidates = staticDir === void 0 ? [
|
|
2188
2299
|
currentDir,
|
|
2189
|
-
(0,
|
|
2190
|
-
(0,
|
|
2191
|
-
(0,
|
|
2192
|
-
(0,
|
|
2193
|
-
(0,
|
|
2194
|
-
] : [staticDir, (0,
|
|
2300
|
+
(0, import_path11.join)(currentDir, "dist"),
|
|
2301
|
+
(0, import_path11.join)(currentDir, "..", "dist"),
|
|
2302
|
+
(0, import_path11.join)(currentDir, "..", "..", "dist"),
|
|
2303
|
+
(0, import_path11.join)(currentDir, ".."),
|
|
2304
|
+
(0, import_path11.join)(currentDir, "..", "..")
|
|
2305
|
+
] : [staticDir, (0, import_path11.join)(staticDir, "dist")];
|
|
2195
2306
|
const resolvedCandidates = await Promise.all(
|
|
2196
2307
|
candidates.map(async (candidate) => ({
|
|
2197
2308
|
candidate,
|
|
2198
|
-
exists: await fileExists((0,
|
|
2309
|
+
exists: await fileExists((0, import_path11.join)(candidate, "index.html"))
|
|
2199
2310
|
}))
|
|
2200
2311
|
);
|
|
2201
2312
|
const resolved = resolvedCandidates.find(({ exists }) => exists);
|
|
@@ -2206,9 +2317,9 @@ async function resolveStaticDir(staticDir) {
|
|
|
2206
2317
|
}
|
|
2207
2318
|
async function resolveReportPath(reportPath) {
|
|
2208
2319
|
if (await isDirectory(reportPath)) {
|
|
2209
|
-
return { reportFile: (0,
|
|
2320
|
+
return { reportFile: (0, import_path11.join)(reportPath, "report.json"), offlineReportDir: reportPath };
|
|
2210
2321
|
}
|
|
2211
|
-
return { reportFile: reportPath, offlineReportDir: (0,
|
|
2322
|
+
return { reportFile: reportPath, offlineReportDir: (0, import_path11.dirname)(reportPath) };
|
|
2212
2323
|
}
|
|
2213
2324
|
async function seedRunContext(routesContext, options) {
|
|
2214
2325
|
if (routesContext.runContext !== void 0) {
|
|
@@ -2223,6 +2334,7 @@ async function setupRoutesContext(options, reportData, staticDir, saveReport, po
|
|
|
2223
2334
|
const { launcher, routesContextOptions } = await resolveRunBackend({
|
|
2224
2335
|
runMode: options.runMode,
|
|
2225
2336
|
docker: options.docker,
|
|
2337
|
+
fontRendering: options.fontRendering,
|
|
2226
2338
|
port
|
|
2227
2339
|
});
|
|
2228
2340
|
const routesContext = createRoutesContext(reportData, staticDir, saveReport, {
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvy/rprtr",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Playwright reporter with visual regression UI for comparing and approving screenshot tests",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"crvy",
|
|
@@ -44,6 +44,12 @@
|
|
|
44
44
|
"require": "./dist/server.cjs",
|
|
45
45
|
"default": "./dist/server.js"
|
|
46
46
|
},
|
|
47
|
+
"./rendering": {
|
|
48
|
+
"types": "./dist/rendering.d.ts",
|
|
49
|
+
"import": "./dist/rendering.js",
|
|
50
|
+
"require": "./dist/rendering.cjs",
|
|
51
|
+
"default": "./dist/rendering.js"
|
|
52
|
+
},
|
|
47
53
|
"./types": {
|
|
48
54
|
"types": "./dist/types.d.ts"
|
|
49
55
|
}
|
|
@@ -69,6 +75,7 @@
|
|
|
69
75
|
"check": "./scripts/check.sh",
|
|
70
76
|
"check:staged": "./scripts/check.sh --staged",
|
|
71
77
|
"example": "cd examples/playwright && bunx playwright test",
|
|
78
|
+
"example:ct": "cd examples/component-testing && bunx playwright test",
|
|
72
79
|
"changelog:preview": "git-cliff --dry-run",
|
|
73
80
|
"changelog:generate": "git-cliff -o CHANGELOG.md",
|
|
74
81
|
"prepublishOnly": "bun run build && bunx publint",
|
|
@@ -83,7 +90,7 @@
|
|
|
83
90
|
"devDependencies": {
|
|
84
91
|
"@playwright/test": "1.59.0",
|
|
85
92
|
"@tailwindcss/postcss": "^4.2.2",
|
|
86
|
-
"@types/bun": "
|
|
93
|
+
"@types/bun": "^1.4.0",
|
|
87
94
|
"@types/ws": "^8.18.1",
|
|
88
95
|
"esbuild": "^0.27.4",
|
|
89
96
|
"esbuild-svelte": "^0.9.4",
|
|
@@ -102,7 +109,7 @@
|
|
|
102
109
|
"@playwright/test": ">=1.40"
|
|
103
110
|
},
|
|
104
111
|
"engines": {
|
|
105
|
-
"bun": ">=1.
|
|
112
|
+
"bun": ">=1.4.0",
|
|
106
113
|
"node": ">=22"
|
|
107
114
|
}
|
|
108
115
|
}
|