@crvy/rprtr 0.3.1 → 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 +24 -0
- package/README.md +43 -1
- package/dist/chunk-7JUICMVX.js +62 -0
- package/dist/{chunk-4YJL655E.js → chunk-7YLKL3SL.js} +6 -1
- package/dist/{chunk-4CVHIHAJ.js → chunk-NXW25MU3.js} +101 -45
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +31 -11
- 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/reporter.cjs +6 -1
- package/dist/reporter.js +1 -1
- 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 +187 -70
- package/dist/server.js +3 -2
- package/dist/snapshot-path-resolver.d.ts.map +1 -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,19 +1339,20 @@ 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;
|
|
1355
|
+
var PLAYWRIGHT_SNAPSHOT_NAME_LIMIT = 100;
|
|
1248
1356
|
function isUnsafeFilePathCharacter(character) {
|
|
1249
1357
|
const codePoint = character.codePointAt(0);
|
|
1250
1358
|
return codePoint !== void 0 && (codePoint <= 44 || codePoint >= 46 && codePoint <= 47 || codePoint >= 58 && codePoint <= 64 || codePoint >= 91 && codePoint <= 96 || codePoint >= 123 && codePoint <= 127);
|
|
@@ -1277,16 +1385,16 @@ function trimLongString(value, length = WINDOWS_FILESYSTEM_FRIENDLY_LENGTH) {
|
|
|
1277
1385
|
const end = length - middle.length - start;
|
|
1278
1386
|
return value.slice(0, start) + middle + value.slice(-end);
|
|
1279
1387
|
}
|
|
1280
|
-
function sanitizeFilePathBeforeExtension(filePath, extension = (0,
|
|
1388
|
+
function sanitizeFilePathBeforeExtension(filePath, extension = (0, import_path5.extname)(filePath)) {
|
|
1281
1389
|
const base = filePath.slice(0, filePath.length - extension.length);
|
|
1282
1390
|
return sanitizeForFilePath(base) + extension;
|
|
1283
1391
|
}
|
|
1284
1392
|
function addSuffixToFilePath(filePath, suffix) {
|
|
1285
|
-
const extension = (0,
|
|
1393
|
+
const extension = (0, import_path5.extname)(filePath);
|
|
1286
1394
|
return filePath.slice(0, filePath.length - extension.length) + suffix + extension;
|
|
1287
1395
|
}
|
|
1288
1396
|
function normalizedSnapshotDir(config) {
|
|
1289
|
-
return (0,
|
|
1397
|
+
return (0, import_path5.resolve)(config.configDir, config.snapshotDir);
|
|
1290
1398
|
}
|
|
1291
1399
|
function templateValue(template, token, value) {
|
|
1292
1400
|
return template.replace(
|
|
@@ -1296,8 +1404,8 @@ function templateValue(template, token, value) {
|
|
|
1296
1404
|
}
|
|
1297
1405
|
function applyTemplate(input, nameArgument, extension) {
|
|
1298
1406
|
const template = input.config.toHaveScreenshotPathTemplate ?? input.config.snapshotPathTemplate ?? DEFAULT_SCREENSHOT_TEMPLATE;
|
|
1299
|
-
const relativeTestFilePath = (0,
|
|
1300
|
-
const parsed = (0,
|
|
1407
|
+
const relativeTestFilePath = (0, import_path5.relative)(input.config.testDir, input.testFile);
|
|
1408
|
+
const parsed = (0, import_path5.parse)(relativeTestFilePath);
|
|
1301
1409
|
const tokens = [
|
|
1302
1410
|
["testDir", input.config.testDir],
|
|
1303
1411
|
["snapshotDir", normalizedSnapshotDir(input.config)],
|
|
@@ -1315,16 +1423,16 @@ function applyTemplate(input, nameArgument, extension) {
|
|
|
1315
1423
|
(currentTemplate, [token, value]) => templateValue(currentTemplate, token, value),
|
|
1316
1424
|
template
|
|
1317
1425
|
);
|
|
1318
|
-
return (0,
|
|
1426
|
+
return (0, import_path5.resolve)(input.config.configDir, snapshotPath);
|
|
1319
1427
|
}
|
|
1320
|
-
function removeExtension(filePath, extension = (0,
|
|
1428
|
+
function removeExtension(filePath, extension = (0, import_path5.extname)(filePath)) {
|
|
1321
1429
|
return filePath.slice(0, filePath.length - extension.length);
|
|
1322
1430
|
}
|
|
1323
1431
|
function snapshotNameParts(declaredName) {
|
|
1324
|
-
const extension = (0,
|
|
1432
|
+
const extension = (0, import_path5.extname)(declaredName) || ".png";
|
|
1325
1433
|
return {
|
|
1326
1434
|
extension,
|
|
1327
|
-
filePath: (0,
|
|
1435
|
+
filePath: (0, import_path5.extname)(declaredName) === "" ? `${declaredName}${extension}` : declaredName
|
|
1328
1436
|
};
|
|
1329
1437
|
}
|
|
1330
1438
|
function filePathForOccurrence(filePath, occurrenceIndex) {
|
|
@@ -1348,7 +1456,7 @@ function resolveStringCallTarget(input, declaration) {
|
|
|
1348
1456
|
function resolveArrayCallTarget(input, declaration) {
|
|
1349
1457
|
const { extension, filePath } = snapshotNameParts(declaration.declaredName);
|
|
1350
1458
|
const occurrenceFilePath = filePathForOccurrence(filePath, declaration.occurrenceIndex);
|
|
1351
|
-
const nameArgument = (0,
|
|
1459
|
+
const nameArgument = (0, import_path5.join)((0, import_path5.dirname)(occurrenceFilePath), (0, import_path5.basename)(occurrenceFilePath, extension));
|
|
1352
1460
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
1353
1461
|
}
|
|
1354
1462
|
function resolveNamedTarget(input, declaration) {
|
|
@@ -1374,7 +1482,11 @@ function reporterTitlesWithoutProjectAndFile(reporterTitlePath2) {
|
|
|
1374
1482
|
return reporterTitlePath2.slice(3).filter((part) => part !== "");
|
|
1375
1483
|
}
|
|
1376
1484
|
function anonymousNameFromTitles(titles, occurrenceIndex) {
|
|
1377
|
-
|
|
1485
|
+
const fullTitleWithoutSpec = `${titles.join(" ")} ${occurrenceIndex}`;
|
|
1486
|
+
return sanitizeFilePathBeforeExtension(
|
|
1487
|
+
`${trimLongString(fullTitleWithoutSpec, PLAYWRIGHT_SNAPSHOT_NAME_LIMIT)}.png`,
|
|
1488
|
+
".png"
|
|
1489
|
+
);
|
|
1378
1490
|
}
|
|
1379
1491
|
function anonymousName(reporterTitlePath2, occurrenceIndex) {
|
|
1380
1492
|
return anonymousNameFromTitles(reporterTitlesWithoutProjectAndFile(reporterTitlePath2), occurrenceIndex);
|
|
@@ -1386,7 +1498,7 @@ function resolveTarget(input, declaration) {
|
|
|
1386
1498
|
case "unnamed": {
|
|
1387
1499
|
const anonymousFileName = anonymousName(input.reporterTitlePath, declaration.occurrenceIndex);
|
|
1388
1500
|
const extension = ".png";
|
|
1389
|
-
const nameArgument = (0,
|
|
1501
|
+
const nameArgument = (0, import_path5.join)((0, import_path5.dirname)(anonymousFileName), (0, import_path5.basename)(anonymousFileName, extension));
|
|
1390
1502
|
return createResolvedBaselineTarget(input, declaration, nameArgument, extension);
|
|
1391
1503
|
}
|
|
1392
1504
|
}
|
|
@@ -1399,7 +1511,7 @@ function resolveBaselineTargets(input) {
|
|
|
1399
1511
|
}
|
|
1400
1512
|
|
|
1401
1513
|
// src/server/utils.ts
|
|
1402
|
-
var
|
|
1514
|
+
var import_path6 = require("path");
|
|
1403
1515
|
var LIVE_UPDATES_WEBSOCKET_PATH = "/";
|
|
1404
1516
|
function broadcastToBrowsers(wsClients, msg) {
|
|
1405
1517
|
const payload = JSON.stringify(msg);
|
|
@@ -1411,10 +1523,10 @@ function isWebSocketUpgradeRequest(req) {
|
|
|
1411
1523
|
return new URL(req.url).pathname === LIVE_UPDATES_WEBSOCKET_PATH && req.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
1412
1524
|
}
|
|
1413
1525
|
function isPathWithinRoots(target, roots) {
|
|
1414
|
-
const resolvedTarget = (0,
|
|
1526
|
+
const resolvedTarget = (0, import_path6.resolve)(target);
|
|
1415
1527
|
return roots.some((root) => {
|
|
1416
|
-
const rel = (0,
|
|
1417
|
-
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);
|
|
1418
1530
|
});
|
|
1419
1531
|
}
|
|
1420
1532
|
|
|
@@ -1432,7 +1544,7 @@ async function handleFile(ctx, req) {
|
|
|
1432
1544
|
let decodedPath;
|
|
1433
1545
|
try {
|
|
1434
1546
|
rawDecoded = decodeURIComponent(new URL(req.url).pathname.slice("/file/".length));
|
|
1435
|
-
decodedPath = (0,
|
|
1547
|
+
decodedPath = (0, import_path7.resolve)(rawDecoded);
|
|
1436
1548
|
} catch {
|
|
1437
1549
|
return notFound();
|
|
1438
1550
|
}
|
|
@@ -1445,7 +1557,7 @@ async function handleFile(ctx, req) {
|
|
|
1445
1557
|
}
|
|
1446
1558
|
return notFound();
|
|
1447
1559
|
}
|
|
1448
|
-
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(
|
|
1449
1561
|
(root) => root !== null
|
|
1450
1562
|
);
|
|
1451
1563
|
if (!isPathWithinRoots(realTarget, realRoots)) {
|
|
@@ -1478,14 +1590,14 @@ function resolveBaselineSnapshotPath(routing, test, retry, imageName) {
|
|
|
1478
1590
|
declarations: [declaration],
|
|
1479
1591
|
config: {
|
|
1480
1592
|
configDir: routing.configDir,
|
|
1481
|
-
testDir: routing.playwrightTestDir ?? (0,
|
|
1482
|
-
snapshotDir: routing.playwrightSnapshotDir ?? (0,
|
|
1593
|
+
testDir: routing.playwrightTestDir ?? (0, import_path7.dirname)(testFile),
|
|
1594
|
+
snapshotDir: routing.playwrightSnapshotDir ?? (0, import_path7.dirname)(testFile),
|
|
1483
1595
|
projectName: test.projectName ?? test.browser,
|
|
1484
1596
|
snapshotSuffix: isContainerPath ? "linux" : process.platform,
|
|
1485
1597
|
snapshotPathTemplate: routing.playwrightSnapshotPathTemplate,
|
|
1486
1598
|
toHaveScreenshotPathTemplate: routing.playwrightToHaveScreenshotPathTemplate
|
|
1487
1599
|
},
|
|
1488
|
-
snapshotPathExists:
|
|
1600
|
+
snapshotPathExists: import_fs3.existsSync
|
|
1489
1601
|
});
|
|
1490
1602
|
return targets.length === 1 ? targets[0]?.snapshotPath ?? null : null;
|
|
1491
1603
|
}
|
|
@@ -1551,7 +1663,7 @@ function enrichDeclaredBaselines(ctx, test) {
|
|
|
1551
1663
|
continue;
|
|
1552
1664
|
}
|
|
1553
1665
|
const snapshotPath = resolveBaselineSnapshotPath(ctx.approvalRouting, test, retry, visualName);
|
|
1554
|
-
if (snapshotPath === null || !(0,
|
|
1666
|
+
if (snapshotPath === null || !(0, import_fs4.existsSync)(snapshotPath)) {
|
|
1555
1667
|
continue;
|
|
1556
1668
|
}
|
|
1557
1669
|
image.expect = `/baseline/${encodeURIComponent(test.id)}/${retry}/${encodeURIComponent(visualName)}`;
|
|
@@ -1656,11 +1768,11 @@ function handleRegister(ctx, rawData) {
|
|
|
1656
1768
|
});
|
|
1657
1769
|
}
|
|
1658
1770
|
function buildRunContext(configFile, data) {
|
|
1659
|
-
const configDir = (0,
|
|
1771
|
+
const configDir = (0, import_path8.dirname)(configFile);
|
|
1660
1772
|
return {
|
|
1661
1773
|
configFile,
|
|
1662
1774
|
cwd: configDir,
|
|
1663
|
-
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))
|
|
1664
1776
|
};
|
|
1665
1777
|
}
|
|
1666
1778
|
|
|
@@ -1693,7 +1805,11 @@ async function resolveRunBackend(options) {
|
|
|
1693
1805
|
console.warn(`[crvy-rprtr] ${message}`);
|
|
1694
1806
|
}
|
|
1695
1807
|
});
|
|
1696
|
-
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 });
|
|
1697
1813
|
return {
|
|
1698
1814
|
launcher,
|
|
1699
1815
|
routesContextOptions: {
|
|
@@ -1704,7 +1820,7 @@ async function resolveRunBackend(options) {
|
|
|
1704
1820
|
}
|
|
1705
1821
|
|
|
1706
1822
|
// src/server/playwright-config.ts
|
|
1707
|
-
var
|
|
1823
|
+
var import_path9 = require("path");
|
|
1708
1824
|
var CONFIG_FILES = [
|
|
1709
1825
|
"playwright.config.ts",
|
|
1710
1826
|
"playwright.config.mts",
|
|
@@ -1716,14 +1832,14 @@ var CONFIG_FILES = [
|
|
|
1716
1832
|
async function resolvePlaywrightConfig(cwd) {
|
|
1717
1833
|
const matches = await Promise.all(
|
|
1718
1834
|
CONFIG_FILES.map(async (file) => {
|
|
1719
|
-
const candidate = (0,
|
|
1835
|
+
const candidate = (0, import_path9.join)(cwd, file);
|
|
1720
1836
|
return await fileExists(candidate) ? candidate : null;
|
|
1721
1837
|
})
|
|
1722
1838
|
);
|
|
1723
1839
|
return matches.find((path) => path !== null) ?? null;
|
|
1724
1840
|
}
|
|
1725
1841
|
function resolveSeedConfigFile(option, cwd) {
|
|
1726
|
-
return option === void 0 ? resolvePlaywrightConfig(cwd) : Promise.resolve((0,
|
|
1842
|
+
return option === void 0 ? resolvePlaywrightConfig(cwd) : Promise.resolve((0, import_path9.resolve)(cwd, option));
|
|
1727
1843
|
}
|
|
1728
1844
|
|
|
1729
1845
|
// src/server/report-persistence.ts
|
|
@@ -1806,7 +1922,7 @@ function createRoutesContext(reportData, staticDir, saveReport, options) {
|
|
|
1806
1922
|
}
|
|
1807
1923
|
|
|
1808
1924
|
// src/server/routes.ts
|
|
1809
|
-
var
|
|
1925
|
+
var import_path10 = require("path");
|
|
1810
1926
|
|
|
1811
1927
|
// src/server/run-routes.ts
|
|
1812
1928
|
function handleRunRoutes(pathname, method, runController, req) {
|
|
@@ -1845,7 +1961,7 @@ function handleApiStop(runController) {
|
|
|
1845
1961
|
|
|
1846
1962
|
// src/server/routes.ts
|
|
1847
1963
|
async function handleRoot(ctx) {
|
|
1848
|
-
const html = await respondWithFile((0,
|
|
1964
|
+
const html = await respondWithFile((0, import_path10.join)(ctx.staticDir, "index.html"), "text/html");
|
|
1849
1965
|
return html ?? new Response("Not Found", { status: 404 });
|
|
1850
1966
|
}
|
|
1851
1967
|
async function handleAppCss() {
|
|
@@ -1869,7 +1985,7 @@ function handleApiReport(ctx) {
|
|
|
1869
1985
|
var APPROVAL_TARGET_ERROR = "Could not resolve approval target";
|
|
1870
1986
|
function actualPathFromUrl(ctx, actualUrl) {
|
|
1871
1987
|
if (actualUrl.startsWith("/screenshots/")) {
|
|
1872
|
-
return (0,
|
|
1988
|
+
return (0, import_path10.join)(ctx.reportData.screenshotDir, actualUrl.slice("/screenshots/".length));
|
|
1873
1989
|
}
|
|
1874
1990
|
if (actualUrl.startsWith("/file/")) {
|
|
1875
1991
|
return decodeURIComponent(actualUrl.slice("/file/".length));
|
|
@@ -1991,7 +2107,7 @@ async function handleScreenshots(ctx, req) {
|
|
|
1991
2107
|
}
|
|
1992
2108
|
async function handleDist(ctx, req) {
|
|
1993
2109
|
const path = new URL(req.url).pathname.slice("/dist/".length);
|
|
1994
|
-
const filePath = (0,
|
|
2110
|
+
const filePath = (0, import_path10.join)(ctx.staticDir, path);
|
|
1995
2111
|
const contentType = filePath.endsWith(".css") ? "text/css" : filePath.endsWith(".js") ? "application/javascript" : filePath.endsWith(".svelte") ? "text/plain" : "application/octet-stream";
|
|
1996
2112
|
const file = await respondWithFile(filePath, contentType);
|
|
1997
2113
|
return file ?? new Response("Not Found", { status: 404 });
|
|
@@ -2178,19 +2294,19 @@ function createWebSocketMessageHandler(getHandlerContext) {
|
|
|
2178
2294
|
};
|
|
2179
2295
|
}
|
|
2180
2296
|
async function resolveStaticDir(staticDir) {
|
|
2181
|
-
const currentDir = (0,
|
|
2297
|
+
const currentDir = (0, import_path11.dirname)((0, import_url.fileURLToPath)(import_meta2.url));
|
|
2182
2298
|
const candidates = staticDir === void 0 ? [
|
|
2183
2299
|
currentDir,
|
|
2184
|
-
(0,
|
|
2185
|
-
(0,
|
|
2186
|
-
(0,
|
|
2187
|
-
(0,
|
|
2188
|
-
(0,
|
|
2189
|
-
] : [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")];
|
|
2190
2306
|
const resolvedCandidates = await Promise.all(
|
|
2191
2307
|
candidates.map(async (candidate) => ({
|
|
2192
2308
|
candidate,
|
|
2193
|
-
exists: await fileExists((0,
|
|
2309
|
+
exists: await fileExists((0, import_path11.join)(candidate, "index.html"))
|
|
2194
2310
|
}))
|
|
2195
2311
|
);
|
|
2196
2312
|
const resolved = resolvedCandidates.find(({ exists }) => exists);
|
|
@@ -2201,9 +2317,9 @@ async function resolveStaticDir(staticDir) {
|
|
|
2201
2317
|
}
|
|
2202
2318
|
async function resolveReportPath(reportPath) {
|
|
2203
2319
|
if (await isDirectory(reportPath)) {
|
|
2204
|
-
return { reportFile: (0,
|
|
2320
|
+
return { reportFile: (0, import_path11.join)(reportPath, "report.json"), offlineReportDir: reportPath };
|
|
2205
2321
|
}
|
|
2206
|
-
return { reportFile: reportPath, offlineReportDir: (0,
|
|
2322
|
+
return { reportFile: reportPath, offlineReportDir: (0, import_path11.dirname)(reportPath) };
|
|
2207
2323
|
}
|
|
2208
2324
|
async function seedRunContext(routesContext, options) {
|
|
2209
2325
|
if (routesContext.runContext !== void 0) {
|
|
@@ -2218,6 +2334,7 @@ async function setupRoutesContext(options, reportData, staticDir, saveReport, po
|
|
|
2218
2334
|
const { launcher, routesContextOptions } = await resolveRunBackend({
|
|
2219
2335
|
runMode: options.runMode,
|
|
2220
2336
|
docker: options.docker,
|
|
2337
|
+
fontRendering: options.fontRendering,
|
|
2221
2338
|
port
|
|
2222
2339
|
});
|
|
2223
2340
|
const routesContext = createRoutesContext(reportData, staticDir, saveReport, {
|
package/dist/server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-path-resolver.d.ts","sourceRoot":"","sources":["../src/snapshot-path-resolver.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"snapshot-path-resolver.d.ts","sourceRoot":"","sources":["../src/snapshot-path-resolver.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAA8B,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAU5F,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC,QAAQ,CAAC,4BAA4B,CAAC,EAAE,MAAM,CAAA;CAC/C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC7C,QAAQ,CAAC,YAAY,EAAE,SAAS,qBAAqB,EAAE,CAAA;IACvD,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAA;IACvC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAA;CAChE;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAeD,iBAAS,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAsBlD;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAqC,GAAG,MAAM,CAW1F;AAED,iBAAS,+BAA+B,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAoB,GAAG,MAAM,CAGhG;AAED,iBAAS,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrE;AA0ID,wBAAgB,6BAA6B,CAC3C,iBAAiB,EAAE,SAAS,MAAM,EAAE,EACpC,eAAe,EAAE,MAAM,GACtB,MAAM,GAAG,IAAI,CAGf;AAqBD,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,SAAS,qBAAqB,EAAE,EAC9C,iBAAiB,EAAE,SAAS,MAAM,EAAE,GACnC,qBAAqB,EAAE,CASzB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,EAAE,CAK7F;AAED,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,+BAA+B,EAAE,mBAAmB,EAAE,CAAA"}
|
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
|
}
|