ptywright 0.6.2 → 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-CRGGdNy8.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-CF2ieCkx.mjs → runner-RhWYhus1.mjs} +204 -102
- package/dist/{server-DpsZDVu2.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);
|
|
@@ -1922,27 +1891,61 @@ function isCodeLikeText(text) {
|
|
|
1922
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);
|
|
1923
1892
|
}
|
|
1924
1893
|
function wrapStableLogicalLine(line, targetCols) {
|
|
1925
|
-
|
|
1894
|
+
const cols = Math.max(1, targetCols);
|
|
1895
|
+
if (cellsDisplayWidth(line.cells) <= cols) return [line];
|
|
1926
1896
|
const rows = [];
|
|
1927
|
-
let
|
|
1928
|
-
|
|
1929
|
-
|
|
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
|
+
}
|
|
1930
1924
|
rows.push({
|
|
1931
|
-
cells,
|
|
1925
|
+
cells: trimTrailingStableCells(rowCells),
|
|
1932
1926
|
live: line.live,
|
|
1933
1927
|
physicalRows: 1
|
|
1934
1928
|
});
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
if (
|
|
1929
|
+
remaining = dropLeadingStableSpaces(nextRemaining);
|
|
1930
|
+
}
|
|
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;
|
|
1943
1937
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
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);
|
|
1946
1949
|
}
|
|
1947
1950
|
function renderStableFrameRow(line, lineCols, lineIndex) {
|
|
1948
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>`;
|
|
@@ -2110,13 +2113,13 @@ async function writeArtifactViewerPages(result, options = {}) {
|
|
|
2110
2113
|
});
|
|
2111
2114
|
}
|
|
2112
2115
|
const domPreviewPathsBySnapshot = new Map(readableArtifacts.filter(({ artifact }) => artifact.kind === "dom").map(({ artifact }) => [artifactSnapshotKey(artifact), artifactDomPreviewPath(artifact)]));
|
|
2113
|
-
const aittyAssets = domPreviewPathsBySnapshot.size > 0 ? prepareAittyReportAssets({
|
|
2114
|
-
artifactsDir: result.artifactsDir,
|
|
2115
|
-
flowPath: result.flowPath,
|
|
2116
|
-
reportPath: result.reportPath
|
|
2117
|
-
}) : null;
|
|
2116
|
+
const aittyAssets = domPreviewPathsBySnapshot.size > 0 ? prepareAittyReportAssets({ artifactsDir: result.artifactsDir }) : null;
|
|
2118
2117
|
const writtenDomPreviewPaths = /* @__PURE__ */ new Set();
|
|
2119
|
-
const
|
|
2118
|
+
const usePtyReplayStableFrameDomPreview = shouldUsePtyReplayStableFrameDomPreview({
|
|
2119
|
+
config: options.config,
|
|
2120
|
+
flowName: result.name
|
|
2121
|
+
});
|
|
2122
|
+
const ptyReplayStableFrame = aittyAssets && usePtyReplayStableFrameDomPreview ? await extractPtyReplayStableFrameForReport({
|
|
2120
2123
|
config: options.config,
|
|
2121
2124
|
result
|
|
2122
2125
|
}) : null;
|
|
@@ -2909,15 +2912,101 @@ async function waitForStableDom(page, args) {
|
|
|
2909
2912
|
throw new Error(`timed out waiting for stable terminal DOM`);
|
|
2910
2913
|
}
|
|
2911
2914
|
async function readTerminalText(page) {
|
|
2912
|
-
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) => {
|
|
2913
2926
|
const node = document.querySelector("[data-terminal-root]");
|
|
2914
2927
|
if (!node) return null;
|
|
2915
2928
|
const rows = Array.from(node.querySelectorAll(".term-grid .term-row"));
|
|
2916
|
-
if (
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
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);
|
|
2921
3010
|
}
|
|
2922
3011
|
async function readTerminalDom(page) {
|
|
2923
3012
|
const dom = await readTerminalDomIfPresent(page);
|
|
@@ -2964,6 +3053,19 @@ async function captureSnapshotStep(ctx, step) {
|
|
|
2964
3053
|
});
|
|
2965
3054
|
continue;
|
|
2966
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
|
+
}
|
|
2967
3069
|
const screenshotPath = join(ctx.artifactsDir, `${base}.png`);
|
|
2968
3070
|
await ctx.page.screenshot({
|
|
2969
3071
|
path: screenshotPath,
|
|
@@ -3123,7 +3225,7 @@ async function captureCassetteFrame(ctx, frame) {
|
|
|
3123
3225
|
stepIndex: frame.stepIndex,
|
|
3124
3226
|
stepType: frame.stepType,
|
|
3125
3227
|
terminalText: normalizeTerminalText(await readTerminalText(ctx.page), ctx.masks),
|
|
3126
|
-
dom:
|
|
3228
|
+
dom: normalizeReplayDom(await readTerminalDom(ctx.page), ctx.masks),
|
|
3127
3229
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3128
3230
|
});
|
|
3129
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
|
}
|