ptywright 0.6.1 → 0.6.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/dist/agent.mjs +1 -1
- package/dist/bin/ptywright.mjs +1 -1
- package/dist/{cli-XLR4BPhA.mjs → cli-uj8xaanE.mjs} +8 -2
- package/dist/cli.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/mcp.mjs +1 -1
- package/dist/{runner-XEimk7TO.mjs → runner-RhWYhus1.mjs} +239 -116
- package/dist/{server-COuf3mW7.mjs → server-B4Bbuluz.mjs} +1 -1
- package/package.json +1 -1
- package/schemas/ptywright-agent-manifest.schema.json +1 -0
- package/schemas/ptywright-agent-replay-summary.schema.json +1 -1
- package/schemas/ptywright-agent-run.schema.json +1 -1
- package/schemas/ptywright-agent.schema.json +1 -1
package/dist/agent.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as formatAgentLaunchPlan, i as runAgentSpecPath, n as replayAgentRecordPath, r as runAgentSpec, t as defaultSpecNameForPath } from "./runner-
|
|
1
|
+
import { a as formatAgentLaunchPlan, i as runAgentSpecPath, n as replayAgentRecordPath, r as runAgentSpec, t as defaultSpecNameForPath } from "./runner-RhWYhus1.mjs";
|
|
2
2
|
export { defaultSpecNameForPath, formatAgentLaunchPlan, replayAgentRecordPath, runAgentSpec, runAgentSpecPath };
|
package/dist/bin/ptywright.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as loadPtywrightConfig } from "./config-bGg636EW.mjs";
|
|
2
|
-
import { C as writeAgentManifestPath, D as escapeHtml, E as escapeAttribute, O as normalizeAgentFlowSpec, S as validateAgentManifestFiles, T as readAgentCassettePath, _ as formatArgv, b as isAgentManifestLike, c as launchAgentBrowser, d as AGENT_RUN_RECORD_SCHEMA_URL, f as agentRunModeSchema, g as writeAgentRunRecordPath, h as readAgentRunRecordPath, i as runAgentSpecPath, k as sanitizeArtifactName, l as createAgentTemplateSpec, m as isAgentRunRecordLike, n as replayAgentRecordPath, o as resolveAgentLaunchTarget, p as formatAgentArgv, s as normalizeAgentFlowSpecWithConfig, u as loadAgentSpec, v as AGENT_MANIFEST_FILE_NAME, w as isAgentCassetteLike, x as readAgentManifestPath, y as agentManifestPath } from "./runner-
|
|
2
|
+
import { C as writeAgentManifestPath, D as escapeHtml, E as escapeAttribute, O as normalizeAgentFlowSpec, S as validateAgentManifestFiles, T as readAgentCassettePath, _ as formatArgv, b as isAgentManifestLike, c as launchAgentBrowser, d as AGENT_RUN_RECORD_SCHEMA_URL, f as agentRunModeSchema, g as writeAgentRunRecordPath, h as readAgentRunRecordPath, i as runAgentSpecPath, k as sanitizeArtifactName, l as createAgentTemplateSpec, m as isAgentRunRecordLike, n as replayAgentRecordPath, o as resolveAgentLaunchTarget, p as formatAgentArgv, s as normalizeAgentFlowSpecWithConfig, u as loadAgentSpec, v as AGENT_MANIFEST_FILE_NAME, w as isAgentCassetteLike, x as readAgentManifestPath, y as agentManifestPath } from "./runner-RhWYhus1.mjs";
|
|
3
3
|
import { a as sameArgv, i as diffCommandMaps, r as formatZodIssues } from "./manifest_files-DW80c1H7.mjs";
|
|
4
4
|
import { i as portableCliPath, n as mergeProcessEnv, o as relativeHref, s as samePath } from "./env-DPYHo-zH.mjs";
|
|
5
5
|
import { d as resolvePtyBackend, u as createDefaultPtyAdapter } from "./runner-BHXXwxYp.mjs";
|
|
6
|
-
import { a as resolveScriptManifestPath, c as relocateScriptManifestCommands, d as resolveScriptRunSummaryPath, f as runScriptPath, i as readScriptManifestPath, l as resolveManifestPrimaryPath$1, n as runAllScripts, o as validateScriptManifest, r as findScriptSummaryManifest, t as createPtywrightServer, u as readScriptRunSummaryPath } from "./server-
|
|
6
|
+
import { a as resolveScriptManifestPath, c as relocateScriptManifestCommands, d as resolveScriptRunSummaryPath, f as runScriptPath, i as readScriptManifestPath, l as resolveManifestPrimaryPath$1, n as runAllScripts, o as validateScriptManifest, r as findScriptSummaryManifest, t as createPtywrightServer, u as readScriptRunSummaryPath } from "./server-B4Bbuluz.mjs";
|
|
7
7
|
import { c as createPtyCassetteReplay, i as formatPtyCassetteInspectLines, l as readPtyCassettePath, o as inspectPtyCassettePath, r as createPtyCassetteRecorder, t as wrapPtyLike, v as validatePtyCassette } from "./pty_like-DWIlWGgA.mjs";
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -418,6 +418,7 @@ const agentReplayFailedArtifactSchema = z.object({
|
|
|
418
418
|
kind: z.enum([
|
|
419
419
|
"terminal",
|
|
420
420
|
"dom",
|
|
421
|
+
"layout",
|
|
421
422
|
"screenshot"
|
|
422
423
|
]),
|
|
423
424
|
path: z.string().min(1),
|
|
@@ -1808,6 +1809,7 @@ async function replayRecordEntry(filePath, artifactsDir, options) {
|
|
|
1808
1809
|
try {
|
|
1809
1810
|
return await replayAgentRecordPath(filePath, {
|
|
1810
1811
|
artifactsDir,
|
|
1812
|
+
config: options.config,
|
|
1811
1813
|
headless: options.headless,
|
|
1812
1814
|
updateSnapshots: options.updateSnapshots
|
|
1813
1815
|
});
|
|
@@ -2023,6 +2025,7 @@ async function replayAllAgentRecords(options = {}) {
|
|
|
2023
2025
|
const artifactsDir = join(suiteDir, "tests", safeArtifactsDirName(relative(dir, filePath)));
|
|
2024
2026
|
const entryStartedAt = Date.now();
|
|
2025
2027
|
const result = await replayRecordEntry(filePath, artifactsDir, {
|
|
2028
|
+
config: options.config,
|
|
2026
2029
|
headless: options.headless ?? true,
|
|
2027
2030
|
updateSnapshots
|
|
2028
2031
|
});
|
|
@@ -2089,6 +2092,7 @@ async function checkAgentRegression(options = {}) {
|
|
|
2089
2092
|
validationAfter: emptyValidationResult(artifactsRoot)
|
|
2090
2093
|
});
|
|
2091
2094
|
const replay = await replayAllAgentRecords({
|
|
2095
|
+
config: options.config,
|
|
2092
2096
|
dir: cassetteDir,
|
|
2093
2097
|
artifactsRoot,
|
|
2094
2098
|
headless: options.headless ?? true,
|
|
@@ -2721,6 +2725,7 @@ async function runAgentRecord(args, context) {
|
|
|
2721
2725
|
}
|
|
2722
2726
|
async function runAgentCheck(args, context) {
|
|
2723
2727
|
return printAgentCheckResult(await checkAgentRegression({
|
|
2728
|
+
config: context.config,
|
|
2724
2729
|
cassetteDir: args.path ?? args.cassetteDir ?? resolveAgentConfigPath(context.config, context.config?.agent?.cassetteDir),
|
|
2725
2730
|
artifactsRoot: args.artifactsRoot ?? resolveAgentConfigPath(context.config, context.config?.agent?.artifactsRoot),
|
|
2726
2731
|
headless: context.headless,
|
|
@@ -2748,6 +2753,7 @@ async function runAgentRerun(args, context) {
|
|
|
2748
2753
|
}
|
|
2749
2754
|
async function runAgentReplayAll(args, context) {
|
|
2750
2755
|
return printAgentReplayAllResult(await replayAllAgentRecords({
|
|
2756
|
+
config: context.config,
|
|
2751
2757
|
dir: args.path ?? resolveAgentConfigPath(context.config, context.config?.agent?.cassetteDir),
|
|
2752
2758
|
artifactsRoot: args.artifactsRoot ?? resolveAgentConfigPath(context.config, context.config?.agent?.artifactsRoot),
|
|
2753
2759
|
headless: context.headless,
|
package/dist/cli.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as main } from "./cli-
|
|
1
|
+
import { t as main } from "./cli-uj8xaanE.mjs";
|
|
2
2
|
export { main };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createPtywrightServer } from "./server-
|
|
1
|
+
import { t as createPtywrightServer } from "./server-B4Bbuluz.mjs";
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
//#region src/index.ts
|
|
4
4
|
const { server, sessions } = createPtywrightServer();
|
package/dist/mcp.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createPtywrightServer } from "./server-
|
|
1
|
+
import { t as createPtywrightServer } from "./server-B4Bbuluz.mjs";
|
|
2
2
|
export { createPtywrightServer };
|
|
@@ -36,6 +36,9 @@ function normalizeTerminalText(input, rules = []) {
|
|
|
36
36
|
function normalizeDomSnapshot(input, rules = []) {
|
|
37
37
|
return applyAgentMasks(input.replace(/\sdata-v-[a-z0-9-]+="[^"]*"/g, "").replace(/\sstyle="[^"]*--term-(?:cell-width|row-height):[^"]*"/g, "").replace(/\s+/g, " ").replace(/>\s+</g, "><").replace(/<div class="[^"]*\bterm-row\b[^"]*\bterm-scrollback-row\b[^"]*"[^>]*><span[^>]*><\/span><\/div>/g, "").trim(), rules, { replacement: escapeHtmlText });
|
|
38
38
|
}
|
|
39
|
+
function normalizeReplayDom(input, rules = []) {
|
|
40
|
+
return applyAgentMasks(input.replace(/\r\n?/g, "\n"), rules, { replacement: escapeHtmlText });
|
|
41
|
+
}
|
|
39
42
|
function sanitizeArtifactName(input) {
|
|
40
43
|
return input.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "artifact";
|
|
41
44
|
}
|
|
@@ -144,6 +147,7 @@ const snapshotStepSchema = z.object({
|
|
|
144
147
|
targets: z.array(z.enum([
|
|
145
148
|
"terminal",
|
|
146
149
|
"dom",
|
|
150
|
+
"layout",
|
|
147
151
|
"screenshot"
|
|
148
152
|
])).optional(),
|
|
149
153
|
fullPage: z.boolean().optional()
|
|
@@ -468,6 +472,7 @@ const agentManifestFileKindSchema = z.enum([
|
|
|
468
472
|
"report",
|
|
469
473
|
"terminal",
|
|
470
474
|
"dom",
|
|
475
|
+
"layout",
|
|
471
476
|
"screenshot",
|
|
472
477
|
"diff"
|
|
473
478
|
]);
|
|
@@ -595,6 +600,7 @@ const agentRunArtifactSchema = z.object({
|
|
|
595
600
|
kind: z.enum([
|
|
596
601
|
"terminal",
|
|
597
602
|
"dom",
|
|
603
|
+
"layout",
|
|
598
604
|
"screenshot"
|
|
599
605
|
]),
|
|
600
606
|
path: z.string().min(1),
|
|
@@ -879,8 +885,8 @@ function errorFields(error) {
|
|
|
879
885
|
//#endregion
|
|
880
886
|
//#region src/agent/aitty_report_assets.ts
|
|
881
887
|
function prepareAittyReportAssets(context) {
|
|
882
|
-
const sources = resolveAittyReportAssetSources(
|
|
883
|
-
if (!existsSync(sources.scriptSource) || !existsSync(sources.styleSource)) throw new Error("@aitty/snapshot report assets are missing.
|
|
888
|
+
const sources = resolveAittyReportAssetSources();
|
|
889
|
+
if (!existsSync(sources.scriptSource) || !existsSync(sources.styleSource)) throw new Error("@aitty/snapshot report assets are missing. Reinstall ptywright dependencies before generating reports.");
|
|
884
890
|
const assetDir = join(context.artifactsDir, "assets");
|
|
885
891
|
const scriptPath = join(assetDir, "aitty-web-component.js");
|
|
886
892
|
const stylePath = join(assetDir, "aitty-terminal.css");
|
|
@@ -889,63 +895,25 @@ function prepareAittyReportAssets(context) {
|
|
|
889
895
|
copyFileSync(sources.styleSource, stylePath);
|
|
890
896
|
return {
|
|
891
897
|
scriptPath,
|
|
892
|
-
scriptType: sources.scriptType,
|
|
893
898
|
stylePath
|
|
894
899
|
};
|
|
895
900
|
}
|
|
896
|
-
function resolveAittyReportAssetSources(
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
901
|
-
const fallback = tryResolveAittyReportAssetSources(createRequire(import.meta.url));
|
|
902
|
-
if (fallback) return fallback;
|
|
903
|
-
throw new Error("@aitty/snapshot report assets are missing. Install @aitty/snapshot or run the package build before generating reports.");
|
|
904
|
-
}
|
|
905
|
-
function resolveAittyReportResolverBases(context) {
|
|
906
|
-
const candidates = [
|
|
907
|
-
findNearestPackageJson(dirname(resolve(context.flowPath))),
|
|
908
|
-
findNearestPackageJson(dirname(resolve(context.reportPath))),
|
|
909
|
-
findNearestPackageJson(dirname(resolve(context.artifactsDir))),
|
|
910
|
-
findNearestPackageJson(process.cwd())
|
|
911
|
-
].filter((path) => Boolean(path));
|
|
912
|
-
return Array.from(new Set(candidates));
|
|
913
|
-
}
|
|
914
|
-
function findNearestPackageJson(startDir) {
|
|
915
|
-
let currentDir = resolve(startDir);
|
|
916
|
-
while (true) {
|
|
917
|
-
const packagePath = join(currentDir, "package.json");
|
|
918
|
-
if (existsSync(packagePath)) return packagePath;
|
|
919
|
-
const parentDir = dirname(currentDir);
|
|
920
|
-
if (parentDir === currentDir) return null;
|
|
921
|
-
currentDir = parentDir;
|
|
922
|
-
}
|
|
901
|
+
function resolveAittyReportAssetSources() {
|
|
902
|
+
const sources = tryResolveAittyReportAssetSources(createRequire(import.meta.url));
|
|
903
|
+
if (!sources) throw new Error("@aitty/snapshot report assets are missing. Install @aitty/snapshot before generating reports.");
|
|
904
|
+
return sources;
|
|
923
905
|
}
|
|
924
906
|
function tryResolveAittyReportAssetSources(resolver) {
|
|
925
|
-
return tryResolveAittyPackageAssetSources(resolver, "@aitty/snapshot") ?? tryResolveAittyPackageAssetSources(resolver, "@aitty/browser");
|
|
926
|
-
}
|
|
927
|
-
function tryResolveAittyPackageAssetSources(resolver, packageName) {
|
|
928
907
|
let scriptSource;
|
|
929
|
-
let scriptType = "classic";
|
|
930
908
|
let styleSource;
|
|
931
909
|
try {
|
|
932
|
-
scriptSource = resolver.resolve(
|
|
933
|
-
|
|
934
|
-
try {
|
|
935
|
-
scriptSource = resolver.resolve(`${packageName}/web-component.js`);
|
|
936
|
-
scriptType = "module";
|
|
937
|
-
} catch {
|
|
938
|
-
return null;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
try {
|
|
942
|
-
styleSource = resolver.resolve(`${packageName}/style.css`);
|
|
910
|
+
scriptSource = resolver.resolve("@aitty/snapshot/web-component.global.js");
|
|
911
|
+
styleSource = resolver.resolve("@aitty/snapshot/style.css");
|
|
943
912
|
} catch {
|
|
944
913
|
return null;
|
|
945
914
|
}
|
|
946
915
|
return {
|
|
947
916
|
scriptSource,
|
|
948
|
-
scriptType,
|
|
949
917
|
styleSource
|
|
950
918
|
};
|
|
951
919
|
}
|
|
@@ -960,13 +928,12 @@ function relativeHref(fromPath, targetPath, artifactsDir) {
|
|
|
960
928
|
function resolveAittyPreviewAssets(previewPath, assets, artifactsDir) {
|
|
961
929
|
return {
|
|
962
930
|
scriptHref: relativeHref(previewPath, assets.scriptPath, artifactsDir),
|
|
963
|
-
scriptType: assets.scriptType,
|
|
964
931
|
styleHref: relativeHref(previewPath, assets.stylePath, artifactsDir)
|
|
965
932
|
};
|
|
966
933
|
}
|
|
967
934
|
function renderAittyPreviewAssetTags(assets) {
|
|
968
935
|
return ` <link rel="stylesheet" href="${escapeAttribute(assets.styleHref)}" />
|
|
969
|
-
<script
|
|
936
|
+
<script src="${escapeAttribute(assets.scriptHref)}"><\/script>
|
|
970
937
|
`;
|
|
971
938
|
}
|
|
972
939
|
function renderAittyPreviewBody(args) {
|
|
@@ -1001,6 +968,7 @@ function renderAittyPreviewCss(viewOptions) {
|
|
|
1001
968
|
//#region src/agent/report_artifact_paths.ts
|
|
1002
969
|
function artifactViewerPath(artifact) {
|
|
1003
970
|
if (artifact.kind === "terminal") return artifact.path.endsWith(".terminal.txt") ? artifact.path.replace(/\.terminal\.txt$/, ".terminal.viewer.html") : `${artifact.path}.viewer.html`;
|
|
971
|
+
if (artifact.kind === "layout") return artifact.path.endsWith(".layout.txt") ? artifact.path.replace(/\.layout\.txt$/, ".layout.viewer.html") : `${artifact.path}.viewer.html`;
|
|
1004
972
|
if (artifact.kind === "dom") return artifact.path.endsWith(".dom.html") ? artifact.path.replace(/\.dom\.html$/, ".dom.viewer.html") : `${artifact.path}.viewer.html`;
|
|
1005
973
|
return null;
|
|
1006
974
|
}
|
|
@@ -1546,6 +1514,28 @@ function normalizeStringArray(value) {
|
|
|
1546
1514
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
1547
1515
|
}
|
|
1548
1516
|
//#endregion
|
|
1517
|
+
//#region src/agent/report_stable_frame_config.ts
|
|
1518
|
+
function resolveStableFrameConfig(config, flowName) {
|
|
1519
|
+
const stableFrames = config?.agent?.report?.stableFrames;
|
|
1520
|
+
const flowConfig = stableFrames?.flows?.[flowName];
|
|
1521
|
+
return {
|
|
1522
|
+
...stableFrames,
|
|
1523
|
+
...flowConfig,
|
|
1524
|
+
previewSource: flowConfig?.previewSource ?? stableFrames?.previewSource ?? "captured-dom",
|
|
1525
|
+
stableMs: flowConfig?.stableMs ?? stableFrames?.stableMs ?? 200,
|
|
1526
|
+
theme: flowConfig?.theme ?? stableFrames?.theme ?? "dark",
|
|
1527
|
+
viewportOnly: flowConfig?.viewportOnly ?? stableFrames?.viewportOnly ?? false,
|
|
1528
|
+
viewportTargets: {
|
|
1529
|
+
...stableFrames?.viewportTargets,
|
|
1530
|
+
...flowConfig?.viewportTargets
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
function shouldUsePtyReplayStableFrameDomPreview(args) {
|
|
1535
|
+
const config = resolveStableFrameConfig(args.config, args.flowName);
|
|
1536
|
+
return config.enabled !== false && !config.skip && config.previewSource === "pty-replay";
|
|
1537
|
+
}
|
|
1538
|
+
//#endregion
|
|
1549
1539
|
//#region src/agent/report_view_options.ts
|
|
1550
1540
|
function isMobileViewport(viewport) {
|
|
1551
1541
|
return Boolean(viewport?.isMobile || viewport?.hasTouch || (viewport?.width ?? 9999) <= 720);
|
|
@@ -1557,8 +1547,11 @@ function resolveReportViewOptions(result, config) {
|
|
|
1557
1547
|
const themeArg = readFlagValueFromArgSets(launchArgSets, "--theme");
|
|
1558
1548
|
const fontSizeArg = readFlagValueFromArgSets(launchArgSets, "--font-size");
|
|
1559
1549
|
const lineHeightArg = readFlagValueFromArgSets(launchArgSets, "--line-height");
|
|
1560
|
-
const stableFrameConfig = resolveStableFrameConfig
|
|
1561
|
-
const themeOverride = ptyReplayArg &&
|
|
1550
|
+
const stableFrameConfig = resolveStableFrameConfig(config, result.name);
|
|
1551
|
+
const themeOverride = ptyReplayArg && shouldUsePtyReplayStableFrameDomPreview({
|
|
1552
|
+
config,
|
|
1553
|
+
flowName: result.name
|
|
1554
|
+
}) ? stableFrameConfig.theme : void 0;
|
|
1562
1555
|
return {
|
|
1563
1556
|
fontSize: parsePositiveNumber(fontSizeArg) ?? 15,
|
|
1564
1557
|
lineHeight: parsePositiveNumber(lineHeightArg) ?? 1.6,
|
|
@@ -1571,15 +1564,6 @@ function parsePositiveNumber(value) {
|
|
|
1571
1564
|
const parsed = Number(value);
|
|
1572
1565
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
1573
1566
|
}
|
|
1574
|
-
function resolveStableFrameConfig$1(config, flowName) {
|
|
1575
|
-
const stableFrames = config?.agent?.report?.stableFrames;
|
|
1576
|
-
const flowConfig = stableFrames?.flows?.[flowName];
|
|
1577
|
-
return {
|
|
1578
|
-
enabled: flowConfig?.enabled ?? stableFrames?.enabled,
|
|
1579
|
-
skip: flowConfig?.skip ?? stableFrames?.skip,
|
|
1580
|
-
theme: flowConfig?.theme ?? stableFrames?.theme ?? "dark"
|
|
1581
|
-
};
|
|
1582
|
-
}
|
|
1583
1567
|
//#endregion
|
|
1584
1568
|
//#region src/agent/report_artifact_viewer.ts
|
|
1585
1569
|
function renderArtifactViewerHtml(args) {
|
|
@@ -1667,21 +1651,6 @@ ${body}
|
|
|
1667
1651
|
</body>
|
|
1668
1652
|
</html>`;
|
|
1669
1653
|
}
|
|
1670
|
-
function resolveStableFrameConfig(config, flowName) {
|
|
1671
|
-
const stableFrames = config?.agent?.report?.stableFrames;
|
|
1672
|
-
const flowConfig = stableFrames?.flows?.[flowName];
|
|
1673
|
-
return {
|
|
1674
|
-
...stableFrames,
|
|
1675
|
-
...flowConfig,
|
|
1676
|
-
stableMs: flowConfig?.stableMs ?? stableFrames?.stableMs ?? 200,
|
|
1677
|
-
theme: flowConfig?.theme ?? stableFrames?.theme ?? "dark",
|
|
1678
|
-
viewportOnly: flowConfig?.viewportOnly ?? stableFrames?.viewportOnly ?? false,
|
|
1679
|
-
viewportTargets: {
|
|
1680
|
-
...stableFrames?.viewportTargets,
|
|
1681
|
-
...flowConfig?.viewportTargets
|
|
1682
|
-
}
|
|
1683
|
-
};
|
|
1684
|
-
}
|
|
1685
1654
|
function resolvePtyReplayPath(replayPathArg, config) {
|
|
1686
1655
|
if (isAbsolute(replayPathArg)) return replayPathArg;
|
|
1687
1656
|
return resolve(config?.rootDir ?? process.cwd(), replayPathArg);
|
|
@@ -1865,29 +1834,50 @@ function renderStableFrameDom(frame, targetCols) {
|
|
|
1865
1834
|
let html = "";
|
|
1866
1835
|
let totalRows = 0;
|
|
1867
1836
|
let wideBlockId = 0;
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
const blockCols = Math.max(targetCols, lineCols);
|
|
1873
|
-
html += `<div class="term-wide-row-block" data-aitty-wide-block="true" data-aitty-wide-block-id="${wideBlockId}" data-aitty-wide-block-kind="viewport-pan" style="--aitty-wide-block-cols: ${blockCols}">`;
|
|
1874
|
-
html += renderStableFrameRow(line, blockCols, totalRows);
|
|
1875
|
-
html += "</div>";
|
|
1876
|
-
totalRows += 1;
|
|
1877
|
-
} else for (const row of wrapStableLogicalLine(line, targetCols)) {
|
|
1837
|
+
let codeRunCols = 0;
|
|
1838
|
+
let codeRunRows = [];
|
|
1839
|
+
const renderPlainRow = (line) => {
|
|
1840
|
+
for (const row of wrapStableLogicalLine(line, targetCols)) {
|
|
1878
1841
|
html += renderStableFrameRow(row, targetCols, totalRows);
|
|
1879
1842
|
totalRows += 1;
|
|
1880
1843
|
}
|
|
1844
|
+
};
|
|
1845
|
+
const flushCodeRun = () => {
|
|
1846
|
+
if (codeRunRows.length === 0) return;
|
|
1847
|
+
if (codeRunCols > targetCols) {
|
|
1848
|
+
wideBlockId += 1;
|
|
1849
|
+
const blockCols = Math.max(targetCols, codeRunCols);
|
|
1850
|
+
html += `<div class="term-wide-row-block" data-aitty-wide-block="true" data-aitty-wide-block-id="${wideBlockId}" data-aitty-wide-block-kind="guttered-code" style="--aitty-wide-block-cols: ${blockCols}">`;
|
|
1851
|
+
for (const row of codeRunRows) {
|
|
1852
|
+
html += renderStableFrameRow(row, blockCols, totalRows);
|
|
1853
|
+
totalRows += 1;
|
|
1854
|
+
}
|
|
1855
|
+
html += "</div>";
|
|
1856
|
+
} else for (const row of codeRunRows) renderPlainRow(row);
|
|
1857
|
+
codeRunCols = 0;
|
|
1858
|
+
codeRunRows = [];
|
|
1859
|
+
};
|
|
1860
|
+
for (const line of frame.lines) {
|
|
1861
|
+
const lineText = stableLineText(line);
|
|
1862
|
+
const lineCols = cellsDisplayWidth(line.cells);
|
|
1863
|
+
if (isGutteredCodeLine(lineText) || isDiffLikeLine(lineText)) {
|
|
1864
|
+
codeRunRows.push(line);
|
|
1865
|
+
codeRunCols = Math.max(codeRunCols, lineCols);
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
flushCodeRun();
|
|
1869
|
+
renderPlainRow(line);
|
|
1881
1870
|
}
|
|
1871
|
+
flushCodeRun();
|
|
1882
1872
|
return `<div class="term-grid" data-cols="${targetCols}" data-rows="${totalRows}" style="--term-cols: ${targetCols}; --term-rows: ${totalRows};">${html}</div>`;
|
|
1883
1873
|
}
|
|
1884
|
-
function shouldRenderViewportPan(line, lineCols, targetCols) {
|
|
1885
|
-
if (lineCols <= targetCols) return false;
|
|
1886
|
-
return isDiffLikeLine(stableLineText(line));
|
|
1887
|
-
}
|
|
1888
1874
|
function stableLineText(line) {
|
|
1889
1875
|
return line.cells.map((cell) => cell.text).join("").trimEnd();
|
|
1890
1876
|
}
|
|
1877
|
+
function isGutteredCodeLine(text) {
|
|
1878
|
+
const normalized = text.replace(/[│┃┆┊▏▕]/g, " ").trimStart();
|
|
1879
|
+
return /^\d+\s+(?:[+-]|\s{2,}\S)/.test(normalized);
|
|
1880
|
+
}
|
|
1891
1881
|
function isDiffLikeLine(text) {
|
|
1892
1882
|
const normalized = text.replace(/[│┃┆┊▏▕]/g, " ").trimStart();
|
|
1893
1883
|
if (normalized === "") return false;
|
|
@@ -1901,27 +1891,61 @@ function isCodeLikeText(text) {
|
|
|
1901
1891
|
return /[{}()[\];=<>]/.test(text) || /\b(?:async|await|class|const|def|export|from|function|if|import|interface|let|return|type|var)\b/.test(text) || /(?:^|\s)(?:--?[a-z][\w-]*|#[\w-]+|\/[A-Za-z0-9._/-]+)(?:\s|$)/.test(text);
|
|
1902
1892
|
}
|
|
1903
1893
|
function wrapStableLogicalLine(line, targetCols) {
|
|
1904
|
-
|
|
1894
|
+
const cols = Math.max(1, targetCols);
|
|
1895
|
+
if (cellsDisplayWidth(line.cells) <= cols) return [line];
|
|
1905
1896
|
const rows = [];
|
|
1906
|
-
let
|
|
1907
|
-
|
|
1908
|
-
|
|
1897
|
+
let remaining = [...line.cells];
|
|
1898
|
+
while (remaining.length > 0) {
|
|
1899
|
+
const chunk = [];
|
|
1900
|
+
let usedCols = 0;
|
|
1901
|
+
let consumed = 0;
|
|
1902
|
+
for (const cell of remaining) {
|
|
1903
|
+
if (chunk.length > 0 && usedCols + cell.width > cols) break;
|
|
1904
|
+
chunk.push(cell);
|
|
1905
|
+
usedCols += cell.width;
|
|
1906
|
+
consumed += 1;
|
|
1907
|
+
if (usedCols >= cols) break;
|
|
1908
|
+
}
|
|
1909
|
+
if (chunk.length === 0) {
|
|
1910
|
+
chunk.push(remaining[0] ?? {
|
|
1911
|
+
style: DEFAULT_STYLE,
|
|
1912
|
+
text: "",
|
|
1913
|
+
width: 1
|
|
1914
|
+
});
|
|
1915
|
+
consumed = 1;
|
|
1916
|
+
}
|
|
1917
|
+
let rowCells = chunk;
|
|
1918
|
+
let nextRemaining = remaining.slice(consumed);
|
|
1919
|
+
const breakIndex = findStableLineBreakIndex(chunk);
|
|
1920
|
+
if (breakIndex > 0) {
|
|
1921
|
+
rowCells = chunk.slice(0, breakIndex);
|
|
1922
|
+
nextRemaining = [...chunk.slice(breakIndex + 1), ...nextRemaining];
|
|
1923
|
+
}
|
|
1909
1924
|
rows.push({
|
|
1910
|
-
cells,
|
|
1925
|
+
cells: trimTrailingStableCells(rowCells),
|
|
1911
1926
|
live: line.live,
|
|
1912
1927
|
physicalRows: 1
|
|
1913
1928
|
});
|
|
1914
|
-
|
|
1915
|
-
cols = 0;
|
|
1916
|
-
};
|
|
1917
|
-
for (const cell of line.cells) {
|
|
1918
|
-
if (cols > 0 && cols + cell.width > targetCols) flush();
|
|
1919
|
-
cells.push(cell);
|
|
1920
|
-
cols += cell.width;
|
|
1921
|
-
if (cols >= targetCols) flush();
|
|
1929
|
+
remaining = dropLeadingStableSpaces(nextRemaining);
|
|
1922
1930
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1931
|
+
return rows.length > 0 ? rows : [line];
|
|
1932
|
+
}
|
|
1933
|
+
function findStableLineBreakIndex(cells) {
|
|
1934
|
+
for (let index = cells.length - 2; index > 0; index -= 1) {
|
|
1935
|
+
const cell = cells[index];
|
|
1936
|
+
if (cell?.text === " " && cell.width === 1) return index;
|
|
1937
|
+
}
|
|
1938
|
+
return -1;
|
|
1939
|
+
}
|
|
1940
|
+
function trimTrailingStableCells(cells) {
|
|
1941
|
+
let end = cells.length;
|
|
1942
|
+
while (end > 0 && cells[end - 1]?.text === " " && cells[end - 1]?.width === 1) end -= 1;
|
|
1943
|
+
return cells.slice(0, end);
|
|
1944
|
+
}
|
|
1945
|
+
function dropLeadingStableSpaces(cells) {
|
|
1946
|
+
let start = 0;
|
|
1947
|
+
while (start < cells.length && cells[start]?.text === " " && cells[start]?.width === 1) start += 1;
|
|
1948
|
+
return cells.slice(start);
|
|
1925
1949
|
}
|
|
1926
1950
|
function renderStableFrameRow(line, lineCols, lineIndex) {
|
|
1927
1951
|
return `<div class="${line.live ? "term-row" : "term-row term-scrollback-row"}"${line.live ? ` data-aitty-live-grid-row="${lineIndex + 1}"` : ""} data-aitty-line-cols="${lineCols}">${renderStableFrameCells(line.cells, lineCols)}</div>`;
|
|
@@ -2089,13 +2113,13 @@ async function writeArtifactViewerPages(result, options = {}) {
|
|
|
2089
2113
|
});
|
|
2090
2114
|
}
|
|
2091
2115
|
const domPreviewPathsBySnapshot = new Map(readableArtifacts.filter(({ artifact }) => artifact.kind === "dom").map(({ artifact }) => [artifactSnapshotKey(artifact), artifactDomPreviewPath(artifact)]));
|
|
2092
|
-
const aittyAssets = domPreviewPathsBySnapshot.size > 0 ? prepareAittyReportAssets({
|
|
2093
|
-
artifactsDir: result.artifactsDir,
|
|
2094
|
-
flowPath: result.flowPath,
|
|
2095
|
-
reportPath: result.reportPath
|
|
2096
|
-
}) : null;
|
|
2116
|
+
const aittyAssets = domPreviewPathsBySnapshot.size > 0 ? prepareAittyReportAssets({ artifactsDir: result.artifactsDir }) : null;
|
|
2097
2117
|
const writtenDomPreviewPaths = /* @__PURE__ */ new Set();
|
|
2098
|
-
const
|
|
2118
|
+
const usePtyReplayStableFrameDomPreview = shouldUsePtyReplayStableFrameDomPreview({
|
|
2119
|
+
config: options.config,
|
|
2120
|
+
flowName: result.name
|
|
2121
|
+
});
|
|
2122
|
+
const ptyReplayStableFrame = aittyAssets && usePtyReplayStableFrameDomPreview ? await extractPtyReplayStableFrameForReport({
|
|
2099
2123
|
config: options.config,
|
|
2100
2124
|
result
|
|
2101
2125
|
}) : null;
|
|
@@ -2888,15 +2912,101 @@ async function waitForStableDom(page, args) {
|
|
|
2888
2912
|
throw new Error(`timed out waiting for stable terminal DOM`);
|
|
2889
2913
|
}
|
|
2890
2914
|
async function readTerminalText(page) {
|
|
2891
|
-
const text = await page
|
|
2915
|
+
const text = await readTerminalProjection(page, "text");
|
|
2916
|
+
if (text === null) throw new Error("terminal root is not attached");
|
|
2917
|
+
return text;
|
|
2918
|
+
}
|
|
2919
|
+
async function readTerminalLayout(page) {
|
|
2920
|
+
const layout = await readTerminalProjection(page, "layout");
|
|
2921
|
+
if (layout === null) throw new Error("terminal root is not attached");
|
|
2922
|
+
return layout;
|
|
2923
|
+
}
|
|
2924
|
+
async function readTerminalProjection(page, mode) {
|
|
2925
|
+
return page.evaluate((projectionMode) => {
|
|
2892
2926
|
const node = document.querySelector("[data-terminal-root]");
|
|
2893
2927
|
if (!node) return null;
|
|
2894
2928
|
const rows = Array.from(node.querySelectorAll(".term-grid .term-row"));
|
|
2895
|
-
if (
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2929
|
+
if (projectionMode === "text") {
|
|
2930
|
+
if (rows.length > 0) return rows.map((row) => serializeTerminalRowText(row)).join("\n");
|
|
2931
|
+
return node.textContent ?? "";
|
|
2932
|
+
}
|
|
2933
|
+
return serializeTerminalLayout(node);
|
|
2934
|
+
function serializeTerminalLayout(root) {
|
|
2935
|
+
const grid = root.querySelector(".term-grid");
|
|
2936
|
+
const target = grid ?? root;
|
|
2937
|
+
const lines = [`# terminal-layout v1 cols=${readElementInteger(grid, "data-cols") ?? "unknown"} rows=${readElementInteger(grid, "data-rows") ?? "unknown"}`];
|
|
2938
|
+
for (const child of Array.from(target.children)) serializeTerminalLayoutElement(child, "", lines);
|
|
2939
|
+
return lines.join("\n");
|
|
2940
|
+
}
|
|
2941
|
+
function serializeTerminalLayoutElement(element, indent, lines) {
|
|
2942
|
+
if (element.classList.contains("term-wide-row-block")) {
|
|
2943
|
+
lines.push(`${indent}wide-block kind=${JSON.stringify(readWideBlockKind(element))} cols=${readWideBlockCols(element) ?? "unknown"}`);
|
|
2944
|
+
for (const child of Array.from(element.children)) serializeTerminalLayoutElement(child, `${indent} `, lines);
|
|
2945
|
+
lines.push(`${indent}end-wide-block`);
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
if (element.classList.contains("term-row")) {
|
|
2949
|
+
lines.push(`${indent}row cols=${readElementInteger(element, "data-aitty-line-cols") ?? "unknown"} text=${JSON.stringify(serializeTerminalRowText(element))}`);
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
for (const child of Array.from(element.children)) serializeTerminalLayoutElement(child, indent, lines);
|
|
2953
|
+
}
|
|
2954
|
+
function readWideBlockKind(element) {
|
|
2955
|
+
if (!(element instanceof HTMLElement)) return "wide";
|
|
2956
|
+
return element.dataset.aittyWideBlockKind || "wide";
|
|
2957
|
+
}
|
|
2958
|
+
function readWideBlockCols(element) {
|
|
2959
|
+
if (!(element instanceof HTMLElement)) return null;
|
|
2960
|
+
const styleCols = element.style.getPropertyValue("--aitty-wide-block-cols").trim();
|
|
2961
|
+
const cols = Number.parseInt(styleCols, 10);
|
|
2962
|
+
return Number.isFinite(cols) && cols > 0 ? cols : null;
|
|
2963
|
+
}
|
|
2964
|
+
function readElementInteger(element, attribute) {
|
|
2965
|
+
const raw = element?.getAttribute(attribute) ?? "";
|
|
2966
|
+
const value = Number.parseInt(raw, 10);
|
|
2967
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
2968
|
+
}
|
|
2969
|
+
function serializeTerminalRowText(row) {
|
|
2970
|
+
let text = "";
|
|
2971
|
+
for (const child of Array.from(row.childNodes)) text += serializeTerminalNodeText(child);
|
|
2972
|
+
return text.trimEnd();
|
|
2973
|
+
}
|
|
2974
|
+
function serializeTerminalNodeText(node) {
|
|
2975
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
2976
|
+
const text = normalizeTerminalTextSegment(node.textContent ?? "");
|
|
2977
|
+
return text.trim().length === 0 ? "" : text;
|
|
2978
|
+
}
|
|
2979
|
+
if (!(node instanceof HTMLElement)) return "";
|
|
2980
|
+
const text = normalizeTerminalTextSegment(node.textContent ?? "");
|
|
2981
|
+
const cols = parseTerminalCellWidth(node);
|
|
2982
|
+
if (cols === null) return text;
|
|
2983
|
+
const width = terminalTextDisplayWidth(text);
|
|
2984
|
+
const padding = Math.max(0, cols - width);
|
|
2985
|
+
return text + " ".repeat(padding);
|
|
2986
|
+
}
|
|
2987
|
+
function normalizeTerminalTextSegment(text) {
|
|
2988
|
+
return text.replace(/\u00a0/g, " ");
|
|
2989
|
+
}
|
|
2990
|
+
function parseTerminalCellWidth(element) {
|
|
2991
|
+
const width = element.style.getPropertyValue("width");
|
|
2992
|
+
if (/^var\(--term-cell-width\b/.test(width)) return 1;
|
|
2993
|
+
const calcMatch = /calc\(\s*var\(--term-cell-width[^)]*\)\s*\*\s*(\d+(?:\.\d+)?)\s*\)/.exec(width);
|
|
2994
|
+
if (!calcMatch) return null;
|
|
2995
|
+
const cols = Number.parseFloat(calcMatch[1] ?? "");
|
|
2996
|
+
return Number.isFinite(cols) && cols > 0 ? Math.round(cols) : null;
|
|
2997
|
+
}
|
|
2998
|
+
function terminalTextDisplayWidth(text) {
|
|
2999
|
+
let width = 0;
|
|
3000
|
+
for (const char of text) {
|
|
3001
|
+
if (/[\u0300-\u036f]/u.test(char)) continue;
|
|
3002
|
+
width += isWideTerminalChar(char) ? 2 : 1;
|
|
3003
|
+
}
|
|
3004
|
+
return width;
|
|
3005
|
+
}
|
|
3006
|
+
function isWideTerminalChar(char) {
|
|
3007
|
+
return /[\u1100-\u115f\u2329\u232a\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe10-\ufe19\ufe30-\ufe6f\uff00-\uff60\uffe0-\uffe6]/u.test(char) || /\p{Extended_Pictographic}/u.test(char);
|
|
3008
|
+
}
|
|
3009
|
+
}, mode);
|
|
2900
3010
|
}
|
|
2901
3011
|
async function readTerminalDom(page) {
|
|
2902
3012
|
const dom = await readTerminalDomIfPresent(page);
|
|
@@ -2943,6 +3053,19 @@ async function captureSnapshotStep(ctx, step) {
|
|
|
2943
3053
|
});
|
|
2944
3054
|
continue;
|
|
2945
3055
|
}
|
|
3056
|
+
if (target === "layout") {
|
|
3057
|
+
const layout = normalizeTerminalText(await readTerminalLayout(ctx.page), ctx.masks);
|
|
3058
|
+
await writeComparableArtifact(ctx, {
|
|
3059
|
+
name: step.name,
|
|
3060
|
+
kind: "layout",
|
|
3061
|
+
relativePath: `${base}.layout.txt`,
|
|
3062
|
+
baselineRelativePath: `${base}.layout.snap.txt`,
|
|
3063
|
+
diffRelativePath: `${base}.layout.diff.txt`,
|
|
3064
|
+
content: layout + "\n",
|
|
3065
|
+
compare: step.compare ?? true
|
|
3066
|
+
});
|
|
3067
|
+
continue;
|
|
3068
|
+
}
|
|
2946
3069
|
const screenshotPath = join(ctx.artifactsDir, `${base}.png`);
|
|
2947
3070
|
await ctx.page.screenshot({
|
|
2948
3071
|
path: screenshotPath,
|
|
@@ -3102,7 +3225,7 @@ async function captureCassetteFrame(ctx, frame) {
|
|
|
3102
3225
|
stepIndex: frame.stepIndex,
|
|
3103
3226
|
stepType: frame.stepType,
|
|
3104
3227
|
terminalText: normalizeTerminalText(await readTerminalText(ctx.page), ctx.masks),
|
|
3105
|
-
dom:
|
|
3228
|
+
dom: normalizeReplayDom(await readTerminalDom(ctx.page), ctx.masks),
|
|
3106
3229
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3107
3230
|
});
|
|
3108
3231
|
}
|
package/package.json
CHANGED
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"properties": {
|
|
130
130
|
"name": { "type": "string", "minLength": 1 },
|
|
131
131
|
"viewport": { "type": "string", "minLength": 1 },
|
|
132
|
-
"kind": { "type": "string", "enum": ["terminal", "dom", "screenshot"] },
|
|
132
|
+
"kind": { "type": "string", "enum": ["terminal", "dom", "layout", "screenshot"] },
|
|
133
133
|
"path": { "type": "string", "minLength": 1 },
|
|
134
134
|
"baselinePath": { "type": "string", "minLength": 1 },
|
|
135
135
|
"diffPath": { "type": "string", "minLength": 1 },
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"properties": {
|
|
114
114
|
"name": { "type": "string", "minLength": 1 },
|
|
115
115
|
"viewport": { "type": "string", "minLength": 1 },
|
|
116
|
-
"kind": { "type": "string", "enum": ["terminal", "dom", "screenshot"] },
|
|
116
|
+
"kind": { "type": "string", "enum": ["terminal", "dom", "layout", "screenshot"] },
|
|
117
117
|
"path": { "type": "string", "minLength": 1 },
|
|
118
118
|
"baselinePath": { "type": "string", "minLength": 1 },
|
|
119
119
|
"diffPath": { "type": "string", "minLength": 1 },
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"compare": { "type": "boolean" },
|
|
124
124
|
"targets": {
|
|
125
125
|
"type": "array",
|
|
126
|
-
"items": { "type": "string", "enum": ["terminal", "dom", "screenshot"] }
|
|
126
|
+
"items": { "type": "string", "enum": ["terminal", "dom", "layout", "screenshot"] }
|
|
127
127
|
},
|
|
128
128
|
"fullPage": { "type": "boolean" }
|
|
129
129
|
}
|