pi-agent-browser-native 0.2.48 → 0.2.49
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 +17 -0
- package/README.md +16 -6
- package/dist/extensions/agent-browser/index.js +785 -0
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
- package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
- package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
- package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
- package/dist/extensions/agent-browser/lib/config.js +122 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
- package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
- package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
- package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
- package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
- package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
- package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
- package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
- package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
- package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
- package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
- package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
- package/dist/extensions/agent-browser/lib/playbook.js +121 -0
- package/dist/extensions/agent-browser/lib/process.js +448 -0
- package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
- package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
- package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
- package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
- package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
- package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
- package/dist/extensions/agent-browser/lib/results/network.js +73 -0
- package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
- package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
- package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
- package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
- package/dist/extensions/agent-browser/lib/results/text.js +40 -0
- package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
- package/dist/extensions/agent-browser/lib/runtime.js +816 -0
- package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
- package/dist/extensions/agent-browser/lib/temp.js +498 -0
- package/dist/extensions/agent-browser/lib/web-search.js +562 -0
- package/docs/RELEASE.md +22 -11
- package/docs/SUPPORT_MATRIX.md +4 -3
- package/package.json +9 -5
- package/scripts/config.mjs +8 -2
- package/scripts/doctor.mjs +8 -7
- package/extensions/agent-browser/index.ts +0 -961
- package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
- package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
- package/extensions/agent-browser/lib/bash-guard.ts +0 -205
- package/extensions/agent-browser/lib/command-policy.ts +0 -71
- package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
- package/extensions/agent-browser/lib/config-policy.js +0 -690
- package/extensions/agent-browser/lib/config.ts +0 -211
- package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
- package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
- package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
- package/extensions/agent-browser/lib/electron/launch.ts +0 -499
- package/extensions/agent-browser/lib/executable-path.ts +0 -19
- package/extensions/agent-browser/lib/fs-utils.ts +0 -18
- package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
- package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
- package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
- package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
- package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
- package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
- package/extensions/agent-browser/lib/input-modes.ts +0 -45
- package/extensions/agent-browser/lib/json-schema.ts +0 -73
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
- package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
- package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
- package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
- package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
- package/extensions/agent-browser/lib/playbook.ts +0 -142
- package/extensions/agent-browser/lib/process.ts +0 -516
- package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
- package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
- package/extensions/agent-browser/lib/results/categories.ts +0 -106
- package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
- package/extensions/agent-browser/lib/results/contracts.ts +0 -241
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
- package/extensions/agent-browser/lib/results/envelope.ts +0 -195
- package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
- package/extensions/agent-browser/lib/results/network.ts +0 -78
- package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
- package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
- package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
- package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
- package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
- package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
- package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
- package/extensions/agent-browser/lib/results/presentation.ts +0 -257
- package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
- package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
- package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
- package/extensions/agent-browser/lib/results/text.ts +0 -40
- package/extensions/agent-browser/lib/runtime.ts +0 -988
- package/extensions/agent-browser/lib/session-page-state.ts +0 -512
- package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
- package/extensions/agent-browser/lib/temp.ts +0 -577
- package/extensions/agent-browser/lib/web-search.ts +0 -728
- /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
|
@@ -1,527 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Compile constrained job and lightweight QA wrapper inputs to upstream batch commands.
|
|
3
|
-
* Responsibilities: Validate job/QA fields, produce argv/stdin, and summarize QA diagnostic results.
|
|
4
|
-
* Scope: Job and QA modes only.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { ArtifactVerificationSummary } from "../results/contracts.js";
|
|
8
|
-
import { isRecord } from "../parsing.js";
|
|
9
|
-
import { summarizeNetworkFailures } from "../results/network.js";
|
|
10
|
-
import { getBatchResultItems, getCommandNameFromBatchItem, getSelectValues } from "./shared.js";
|
|
11
|
-
import { compileAgentBrowserSemanticAction } from "./semantic-action.js";
|
|
12
|
-
import {
|
|
13
|
-
AGENT_BROWSER_JOB_STEP_ACTIONS,
|
|
14
|
-
AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS,
|
|
15
|
-
AGENT_BROWSER_QA_LOAD_STATES,
|
|
16
|
-
type AgentBrowserJobStepAction,
|
|
17
|
-
type AgentBrowserQaLoadState,
|
|
18
|
-
type AgentBrowserQaPresetAnalysis,
|
|
19
|
-
type CompiledAgentBrowserJob,
|
|
20
|
-
type CompiledAgentBrowserJobStep,
|
|
21
|
-
type CompiledAgentBrowserQaPreset,
|
|
22
|
-
} from "./types.js";
|
|
23
|
-
|
|
24
|
-
function getRequiredJobString(step: Record<string, unknown>, field: "path" | "selector" | "text" | "url", action: AgentBrowserJobStepAction): { value?: string; error?: string } {
|
|
25
|
-
const value = step[field];
|
|
26
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
27
|
-
return { error: `job step ${action} requires a non-empty ${field} string.` };
|
|
28
|
-
}
|
|
29
|
-
return { value };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function compileJobClickOrFillStep(step: Record<string, unknown>, action: "click" | "fill"): { args?: string[]; error?: string } {
|
|
33
|
-
const hasSelector = typeof step.selector === "string" && step.selector.trim().length > 0;
|
|
34
|
-
const hasLocator = step.locator !== undefined || step.role !== undefined || step.name !== undefined || step.value !== undefined;
|
|
35
|
-
if (hasSelector && hasLocator) {
|
|
36
|
-
return { error: `job step ${action} must use either selector or semantic locator fields, not both.` };
|
|
37
|
-
}
|
|
38
|
-
if (hasSelector) {
|
|
39
|
-
if (action === "click") return { args: ["click", step.selector as string] };
|
|
40
|
-
const text = getRequiredJobString(step, "text", action);
|
|
41
|
-
if (text.error) return { error: text.error };
|
|
42
|
-
return { args: ["fill", step.selector as string, text.value as string] };
|
|
43
|
-
}
|
|
44
|
-
if (!hasLocator) {
|
|
45
|
-
return { error: `job step ${action} requires either a non-empty selector string or semantic locator fields.` };
|
|
46
|
-
}
|
|
47
|
-
const compiled = compileAgentBrowserSemanticAction({
|
|
48
|
-
action,
|
|
49
|
-
locator: step.locator,
|
|
50
|
-
name: step.name,
|
|
51
|
-
role: step.role,
|
|
52
|
-
text: step.text,
|
|
53
|
-
value: step.value,
|
|
54
|
-
});
|
|
55
|
-
if (compiled.error) return { error: compiled.error.replaceAll("semanticAction", `job step ${action}`) };
|
|
56
|
-
return { args: compiled.compiled?.args };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function getUnsupportedJobStepField(step: Record<string, unknown>, allowedFields: ReadonlySet<string>): string | undefined {
|
|
60
|
-
return Object.keys(step).find((field) => !allowedFields.has(field));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function getUnsupportedJobStepFieldError(step: Record<string, unknown>, action: AgentBrowserJobStepAction, allowedFields: ReadonlySet<string>): string | undefined {
|
|
64
|
-
const unsupportedField = getUnsupportedJobStepField(step, allowedFields);
|
|
65
|
-
if (!unsupportedField) return undefined;
|
|
66
|
-
const supportedFields = [...allowedFields].filter((field) => field !== "action");
|
|
67
|
-
const supportedText = supportedFields.length > 0 ? `supported fields are ${supportedFields.join(", ")}.` : "no additional fields are supported.";
|
|
68
|
-
return `job step ${action} does not support ${unsupportedField}; ${supportedText}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const JOB_STEP_ALLOWED_FIELDS = {
|
|
72
|
-
assertText: new Set(["action", "text"]),
|
|
73
|
-
assertUrl: new Set(["action", "url"]),
|
|
74
|
-
click: new Set(["action", "locator", "name", "role", "selector", "value"]),
|
|
75
|
-
fill: new Set(["action", "locator", "name", "role", "selector", "text", "value"]),
|
|
76
|
-
open: new Set(["action", "loadState", "url"]),
|
|
77
|
-
screenshot: new Set(["action", "path"]),
|
|
78
|
-
select: new Set(["action", "selector", "value", "values"]),
|
|
79
|
-
snapshot: new Set(["action"]),
|
|
80
|
-
type: new Set(["action", "delayMs", "press", "selector", "text"]),
|
|
81
|
-
wait: new Set(["action", "milliseconds"]),
|
|
82
|
-
waitForDownload: new Set(["action", "path"]),
|
|
83
|
-
} satisfies Record<AgentBrowserJobStepAction, ReadonlySet<string>>;
|
|
84
|
-
|
|
85
|
-
type CompileJobStepResult = {
|
|
86
|
-
args?: string[];
|
|
87
|
-
error?: string;
|
|
88
|
-
extraSteps?: CompiledAgentBrowserJobStep[];
|
|
89
|
-
generatedFrom?: string;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
type JobStepCompiler = (step: Record<string, unknown>, index: number) => CompileJobStepResult;
|
|
93
|
-
|
|
94
|
-
type JobStepDescriptor = {
|
|
95
|
-
allowedFields: ReadonlySet<string>;
|
|
96
|
-
compile: JobStepCompiler;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
function globUrlPatternToRegexSource(pattern: string): string {
|
|
100
|
-
let source = "^";
|
|
101
|
-
for (let index = 0; index < pattern.length; index += 1) {
|
|
102
|
-
const char = pattern.charAt(index);
|
|
103
|
-
if (char === "*") {
|
|
104
|
-
let runLength = 1;
|
|
105
|
-
while (pattern[index + runLength] === "*") runLength += 1;
|
|
106
|
-
source += runLength === 1 ? "[^/]*" : ".*";
|
|
107
|
-
index += runLength - 1;
|
|
108
|
-
} else {
|
|
109
|
-
source += char.replace(/[\\^$+?.()|[\]{}]/g, "\\$&");
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return `${source}$`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function compileJobAssertUrlArgs(url: string): string[] {
|
|
116
|
-
if (!url.includes("*")) return ["wait", "--url", url];
|
|
117
|
-
return ["wait", "--fn", `new RegExp(${JSON.stringify(globUrlPatternToRegexSource(url))}).test(location.href)`];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function compileJobTypeSteps(step: Record<string, unknown>): { error?: string; steps?: CompiledAgentBrowserJobStep[] } {
|
|
121
|
-
const text = getRequiredJobString(step, "text", "type");
|
|
122
|
-
if (text.error) return { error: text.error };
|
|
123
|
-
const selector = step.selector;
|
|
124
|
-
if (selector !== undefined && (typeof selector !== "string" || selector.trim().length === 0)) {
|
|
125
|
-
return { error: "job step type selector must be a non-empty string when provided." };
|
|
126
|
-
}
|
|
127
|
-
const delayMs = step.delayMs;
|
|
128
|
-
if (delayMs !== undefined && (typeof delayMs !== "number" || !Number.isInteger(delayMs) || delayMs <= 0)) {
|
|
129
|
-
return { error: "job step type delayMs must be a positive integer when provided." };
|
|
130
|
-
}
|
|
131
|
-
const press = step.press;
|
|
132
|
-
if (press !== undefined && (typeof press !== "string" || press.trim().length === 0)) {
|
|
133
|
-
return { error: "job step type press must be a non-empty key string when provided." };
|
|
134
|
-
}
|
|
135
|
-
const typedText = text.value as string;
|
|
136
|
-
const typedChars = Array.from(typedText);
|
|
137
|
-
if (typedChars.length === 0) return { error: "job step type requires non-empty text." };
|
|
138
|
-
if (delayMs !== undefined && typedChars.length > AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS) {
|
|
139
|
-
return { error: `job step type delayMs supports at most ${AGENT_BROWSER_JOB_TYPE_DELAYED_TEXT_MAX_CHARACTERS} characters; split longer text into shorter calls or omit delayMs.` };
|
|
140
|
-
}
|
|
141
|
-
const compiledSteps: CompiledAgentBrowserJobStep[] = [];
|
|
142
|
-
if (delayMs === undefined) {
|
|
143
|
-
compiledSteps.push({ action: "type", args: typeof selector === "string" ? ["type", selector, typedText] : ["keyboard", "type", typedText] });
|
|
144
|
-
} else {
|
|
145
|
-
if (typeof selector === "string") compiledSteps.push({ action: "type", args: ["focus", selector], generatedFrom: "type.selector" });
|
|
146
|
-
for (const [index, char] of typedChars.entries()) {
|
|
147
|
-
compiledSteps.push({ action: "type", args: ["keyboard", "type", char], generatedFrom: "type.delayMs" });
|
|
148
|
-
if (index < typedChars.length - 1) compiledSteps.push({ action: "wait", args: ["wait", String(delayMs)], generatedFrom: "type.delayMs" });
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (typeof press === "string") compiledSteps.push({ action: "type", args: ["press", press], generatedFrom: "type.press" });
|
|
152
|
-
return { steps: compiledSteps };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function compileOpenJobStep(step: Record<string, unknown>, index: number): CompileJobStepResult {
|
|
156
|
-
const result = getRequiredJobString(step, "url", "open");
|
|
157
|
-
if (result.error) return { error: result.error };
|
|
158
|
-
const extraSteps: CompiledAgentBrowserJobStep[] = [];
|
|
159
|
-
if (step.loadState !== undefined) {
|
|
160
|
-
if (typeof step.loadState !== "string" || !AGENT_BROWSER_QA_LOAD_STATES.includes(step.loadState as AgentBrowserQaLoadState)) {
|
|
161
|
-
return { error: `job.steps[${index}].loadState must be one of: ${AGENT_BROWSER_QA_LOAD_STATES.join(", ")}.` };
|
|
162
|
-
}
|
|
163
|
-
extraSteps.push({ action: "wait", args: ["wait", "--load", step.loadState], generatedFrom: "open.loadState" });
|
|
164
|
-
}
|
|
165
|
-
return { args: ["open", result.value as string], extraSteps };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function compileClickJobStep(step: Record<string, unknown>): CompileJobStepResult {
|
|
169
|
-
return compileJobClickOrFillStep(step, "click");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function compileFillJobStep(step: Record<string, unknown>): CompileJobStepResult {
|
|
173
|
-
return compileJobClickOrFillStep(step, "fill");
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function compileTypeJobStep(step: Record<string, unknown>): CompileJobStepResult {
|
|
177
|
-
const result = compileJobTypeSteps(step);
|
|
178
|
-
if (result.error) return { error: result.error };
|
|
179
|
-
const [firstStep, ...extraSteps] = result.steps as CompiledAgentBrowserJobStep[];
|
|
180
|
-
return { args: firstStep.args, extraSteps, generatedFrom: firstStep.generatedFrom };
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function compileSelectJobStep(step: Record<string, unknown>, index: number): CompileJobStepResult {
|
|
184
|
-
const selector = getRequiredJobString(step, "selector", "select");
|
|
185
|
-
if (selector.error) return { error: selector.error };
|
|
186
|
-
const values = getSelectValues(step, `job.steps[${index}]`);
|
|
187
|
-
if (values.error) return { error: values.error };
|
|
188
|
-
return { args: ["select", selector.value as string, ...(values.values as string[])] };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function compileWaitJobStep(step: Record<string, unknown>): CompileJobStepResult {
|
|
192
|
-
const milliseconds = step.milliseconds;
|
|
193
|
-
if (typeof milliseconds !== "number" || !Number.isInteger(milliseconds) || milliseconds <= 0) {
|
|
194
|
-
return { error: "job step wait requires a positive integer milliseconds value." };
|
|
195
|
-
}
|
|
196
|
-
return { args: ["wait", String(milliseconds)] };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function compileAssertTextJobStep(step: Record<string, unknown>): CompileJobStepResult {
|
|
200
|
-
const result = getRequiredJobString(step, "text", "assertText");
|
|
201
|
-
if (result.error) return { error: result.error };
|
|
202
|
-
return { args: ["wait", "--text", result.value as string] };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function compileAssertUrlJobStep(step: Record<string, unknown>): CompileJobStepResult {
|
|
206
|
-
const result = getRequiredJobString(step, "url", "assertUrl");
|
|
207
|
-
if (result.error) return { error: result.error };
|
|
208
|
-
return { args: compileJobAssertUrlArgs(result.value as string) };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function compilePathArtifactJobStep(step: Record<string, unknown>, action: "screenshot" | "waitForDownload"): CompileJobStepResult {
|
|
212
|
-
const result = getRequiredJobString(step, "path", action);
|
|
213
|
-
if (result.error) return { error: result.error };
|
|
214
|
-
return { args: action === "waitForDownload" ? ["wait", "--download", result.value as string] : ["screenshot", result.value as string] };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const JOB_STEP_DESCRIPTORS: Record<AgentBrowserJobStepAction, JobStepDescriptor> = {
|
|
218
|
-
assertText: { allowedFields: JOB_STEP_ALLOWED_FIELDS.assertText, compile: compileAssertTextJobStep },
|
|
219
|
-
assertUrl: { allowedFields: JOB_STEP_ALLOWED_FIELDS.assertUrl, compile: compileAssertUrlJobStep },
|
|
220
|
-
click: { allowedFields: JOB_STEP_ALLOWED_FIELDS.click, compile: compileClickJobStep },
|
|
221
|
-
fill: { allowedFields: JOB_STEP_ALLOWED_FIELDS.fill, compile: compileFillJobStep },
|
|
222
|
-
open: { allowedFields: JOB_STEP_ALLOWED_FIELDS.open, compile: compileOpenJobStep },
|
|
223
|
-
screenshot: { allowedFields: JOB_STEP_ALLOWED_FIELDS.screenshot, compile: (step) => compilePathArtifactJobStep(step, "screenshot") },
|
|
224
|
-
select: { allowedFields: JOB_STEP_ALLOWED_FIELDS.select, compile: compileSelectJobStep },
|
|
225
|
-
snapshot: { allowedFields: JOB_STEP_ALLOWED_FIELDS.snapshot, compile: () => ({ args: ["snapshot", "-i"] }) },
|
|
226
|
-
type: { allowedFields: JOB_STEP_ALLOWED_FIELDS.type, compile: compileTypeJobStep },
|
|
227
|
-
wait: { allowedFields: JOB_STEP_ALLOWED_FIELDS.wait, compile: compileWaitJobStep },
|
|
228
|
-
waitForDownload: { allowedFields: JOB_STEP_ALLOWED_FIELDS.waitForDownload, compile: (step) => compilePathArtifactJobStep(step, "waitForDownload") },
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
export function compileAgentBrowserJob(input: unknown): { compiled?: CompiledAgentBrowserJob; error?: string } {
|
|
232
|
-
if (!isRecord(input)) {
|
|
233
|
-
return { error: "job must be an object." };
|
|
234
|
-
}
|
|
235
|
-
const rawFailFast = input.failFast;
|
|
236
|
-
if (rawFailFast !== undefined && typeof rawFailFast !== "boolean") {
|
|
237
|
-
return { error: "job.failFast must be a boolean when provided." };
|
|
238
|
-
}
|
|
239
|
-
const failFast = rawFailFast !== false;
|
|
240
|
-
const rawSteps = input.steps;
|
|
241
|
-
if (!Array.isArray(rawSteps) || rawSteps.length === 0) {
|
|
242
|
-
return { error: "job.steps must be a non-empty array." };
|
|
243
|
-
}
|
|
244
|
-
const steps: CompiledAgentBrowserJobStep[] = [];
|
|
245
|
-
for (const [index, rawStep] of rawSteps.entries()) {
|
|
246
|
-
if (!isRecord(rawStep)) {
|
|
247
|
-
return { error: `job.steps[${index}] must be an object.` };
|
|
248
|
-
}
|
|
249
|
-
const action = rawStep.action;
|
|
250
|
-
if (typeof action !== "string" || !AGENT_BROWSER_JOB_STEP_ACTIONS.includes(action as AgentBrowserJobStepAction)) {
|
|
251
|
-
return { error: `job.steps[${index}].action must be one of: ${AGENT_BROWSER_JOB_STEP_ACTIONS.join(", ")}.` };
|
|
252
|
-
}
|
|
253
|
-
const jobAction = action as AgentBrowserJobStepAction;
|
|
254
|
-
const descriptor = JOB_STEP_DESCRIPTORS[jobAction];
|
|
255
|
-
const unsupportedFieldError = getUnsupportedJobStepFieldError(rawStep, jobAction, descriptor.allowedFields);
|
|
256
|
-
if (unsupportedFieldError) return { error: `job.steps[${index}]: ${unsupportedFieldError}` };
|
|
257
|
-
const compiledStep = descriptor.compile(rawStep, index);
|
|
258
|
-
if (compiledStep.error) return { error: compiledStep.error.startsWith(`job.steps[${index}]`) ? compiledStep.error : `job.steps[${index}]: ${compiledStep.error}` };
|
|
259
|
-
steps.push({ action: jobAction, args: compiledStep.args as string[], generatedFrom: compiledStep.generatedFrom }, ...(compiledStep.extraSteps ?? []));
|
|
260
|
-
}
|
|
261
|
-
return { compiled: { args: failFast ? ["batch", "--bail"] : ["batch"], failFast, stdin: JSON.stringify(steps.map((step) => step.args)), steps } };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export function isHttpOrHttpsUrl(url: string): boolean {
|
|
265
|
-
try {
|
|
266
|
-
const protocol = new URL(url).protocol;
|
|
267
|
-
return protocol === "http:" || protocol === "https:";
|
|
268
|
-
} catch {
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function describeQaChecksRun(checks: CompiledAgentBrowserQaPreset["checks"]): string {
|
|
274
|
-
const parts = [`load:${checks.loadState}`];
|
|
275
|
-
if (checks.expectedText.length > 0) parts.push(`text×${checks.expectedText.length}`);
|
|
276
|
-
if (checks.expectedSelector) parts.push("selector");
|
|
277
|
-
if (checks.checkNetwork) parts.push("network");
|
|
278
|
-
if (checks.checkConsole) parts.push("console");
|
|
279
|
-
if (checks.checkErrors) parts.push("errors");
|
|
280
|
-
if (checks.diagnosticsResetAtStart) parts.push("diagnostics-reset");
|
|
281
|
-
else if (checks.checkNetwork || checks.checkConsole || checks.checkErrors) parts.push("attached-diagnostics-preserved");
|
|
282
|
-
if (checks.screenshotPath) parts.push("screenshot");
|
|
283
|
-
return parts.join(", ");
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export function extractQaPageContext(options: {
|
|
287
|
-
attachedTarget?: { title?: string; url?: string };
|
|
288
|
-
batchData?: unknown;
|
|
289
|
-
compiled?: CompiledAgentBrowserQaPreset;
|
|
290
|
-
}): { title?: string; url?: string } {
|
|
291
|
-
if (options.attachedTarget?.title || options.attachedTarget?.url) {
|
|
292
|
-
return { title: options.attachedTarget.title, url: options.attachedTarget.url };
|
|
293
|
-
}
|
|
294
|
-
for (const item of getBatchResultItems(options.batchData)) {
|
|
295
|
-
if (getCommandNameFromBatchItem(item) !== "open" || !isRecord(item.result)) continue;
|
|
296
|
-
const url = typeof item.result.url === "string" ? item.result.url : undefined;
|
|
297
|
-
const title = typeof item.result.title === "string" ? item.result.title : undefined;
|
|
298
|
-
if (url || title) return { title, url };
|
|
299
|
-
}
|
|
300
|
-
if (options.compiled?.checks.url) {
|
|
301
|
-
return { url: options.compiled.checks.url };
|
|
302
|
-
}
|
|
303
|
-
return {};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
export function buildQaCompactPassText(options: {
|
|
307
|
-
artifactVerification?: ArtifactVerificationSummary;
|
|
308
|
-
batchStepCount: number;
|
|
309
|
-
checks: CompiledAgentBrowserQaPreset["checks"];
|
|
310
|
-
page?: { title?: string; url?: string };
|
|
311
|
-
qaPreset: AgentBrowserQaPresetAnalysis;
|
|
312
|
-
}): string {
|
|
313
|
-
const lines = [options.qaPreset.summary];
|
|
314
|
-
const pageParts = [options.page?.title, options.page?.url].filter((part): part is string => typeof part === "string" && part.length > 0);
|
|
315
|
-
if (pageParts.length > 0) lines.push(`Page: ${pageParts.join(" — ")}`);
|
|
316
|
-
lines.push(`Checks run: ${describeQaChecksRun(options.checks)} (${options.batchStepCount} batch step${options.batchStepCount === 1 ? "" : "s"})`);
|
|
317
|
-
if (options.checks.attached && !options.checks.diagnosticsResetAtStart && (options.checks.checkNetwork || options.checks.checkConsole || options.checks.checkErrors)) {
|
|
318
|
-
lines.push("Attached diagnostics: existing upstream session console/network/error buffers were preserved; rows may include events from before qa.attached started.");
|
|
319
|
-
}
|
|
320
|
-
if (options.checks.screenshotPath) {
|
|
321
|
-
const verification = options.artifactVerification;
|
|
322
|
-
lines.push(verification
|
|
323
|
-
? `Screenshot: ${options.checks.screenshotPath} (${verification.verifiedCount}/${verification.artifacts.length} verified on disk)`
|
|
324
|
-
: `Screenshot: ${options.checks.screenshotPath}`);
|
|
325
|
-
}
|
|
326
|
-
lines.push("Full diagnostic matrix: see details.qaPreset and details.batchSteps.");
|
|
327
|
-
return lines.join("\n");
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const QA_VISIBLE_TEXT_TIMEOUT_MS = 5_000;
|
|
331
|
-
|
|
332
|
-
function formatQaExpectedTextPreview(text: string): string {
|
|
333
|
-
return JSON.stringify(text.length > 80 ? `${text.slice(0, 77)}...` : text);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function buildQaVisibleTextPredicate(text: string): string {
|
|
337
|
-
return `(() => {
|
|
338
|
-
const expected = ${JSON.stringify(text)}.replace(/\\s+/g, " ").trim();
|
|
339
|
-
if (!expected) return false;
|
|
340
|
-
const root = document.body || document.documentElement;
|
|
341
|
-
if (!root) return false;
|
|
342
|
-
const skipTags = new Set(["SCRIPT", "STYLE", "NOSCRIPT", "SVG"]);
|
|
343
|
-
const normalize = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();
|
|
344
|
-
const isVisibleElement = (element) => {
|
|
345
|
-
if (!(element instanceof HTMLElement)) return false;
|
|
346
|
-
if (skipTags.has(element.tagName)) return false;
|
|
347
|
-
const style = window.getComputedStyle(element);
|
|
348
|
-
if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0) return false;
|
|
349
|
-
return element.getClientRects().length > 0;
|
|
350
|
-
};
|
|
351
|
-
const hasVisibleAncestors = (node) => {
|
|
352
|
-
for (let element = node.parentElement; element; element = element.parentElement) {
|
|
353
|
-
if (!isVisibleElement(element)) return false;
|
|
354
|
-
if (element === root) break;
|
|
355
|
-
}
|
|
356
|
-
return true;
|
|
357
|
-
};
|
|
358
|
-
const textWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
359
|
-
let visitedText = 0;
|
|
360
|
-
for (let node = textWalker.nextNode(); node && visitedText < 6000; node = textWalker.nextNode(), visitedText += 1) {
|
|
361
|
-
if (!hasVisibleAncestors(node)) continue;
|
|
362
|
-
if (normalize(node.nodeValue).includes(expected)) return true;
|
|
363
|
-
}
|
|
364
|
-
const elementWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
365
|
-
let visitedElements = 0;
|
|
366
|
-
for (let node = elementWalker.nextNode(); node && visitedElements < 3000; node = elementWalker.nextNode(), visitedElements += 1) {
|
|
367
|
-
const element = node;
|
|
368
|
-
if (!isVisibleElement(element) || !("value" in element)) continue;
|
|
369
|
-
if (normalize(element.value).includes(expected)) return true;
|
|
370
|
-
}
|
|
371
|
-
return false;
|
|
372
|
-
})()`;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function qaVisibleTextWaitPassed(item: ReturnType<typeof getBatchResultItems>[number] | undefined, step: CompiledAgentBrowserJobStep): boolean | undefined {
|
|
376
|
-
if (step.args[0] !== "wait" || step.args[1] !== "--fn") return undefined;
|
|
377
|
-
if (!item || item.success === false) return false;
|
|
378
|
-
if (typeof item.result === "boolean") return item.result;
|
|
379
|
-
if (isRecord(item.result) && typeof item.result.result === "boolean") return item.result.result;
|
|
380
|
-
return true;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function extractQaTextAssertionResultText(item: ReturnType<typeof getBatchResultItems>[number] | undefined): string | undefined {
|
|
384
|
-
if (!item || item.success === false) return undefined;
|
|
385
|
-
const result = item.result;
|
|
386
|
-
if (typeof result === "string") return result;
|
|
387
|
-
if (!isRecord(result)) return undefined;
|
|
388
|
-
for (const key of ["result", "text", "value"] as const) {
|
|
389
|
-
const value = result[key];
|
|
390
|
-
if (typeof value === "string") return value;
|
|
391
|
-
}
|
|
392
|
-
return undefined;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
export function analyzeQaPresetTimeout(compiled: CompiledAgentBrowserQaPreset): AgentBrowserQaPresetAnalysis | undefined {
|
|
396
|
-
if (compiled.checks.expectedText.length === 0) return undefined;
|
|
397
|
-
const failedChecks = compiled.checks.expectedText.map((text) => `expected text was not verified before timeout: ${formatQaExpectedTextPreview(text)}`);
|
|
398
|
-
return {
|
|
399
|
-
failedChecks,
|
|
400
|
-
passed: false,
|
|
401
|
-
summary: `QA preset failed: ${failedChecks.join("; ")}.`,
|
|
402
|
-
warnings: ["The wrapper timed out before expected-text evidence could be verified; inspect timeoutPartialProgress and retry with a narrower readiness condition if the page was still loading."],
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
export function analyzeQaPresetResults(data: unknown, compiled?: CompiledAgentBrowserQaPreset): AgentBrowserQaPresetAnalysis | undefined {
|
|
407
|
-
const items = getBatchResultItems(data);
|
|
408
|
-
if (items.length === 0) return undefined;
|
|
409
|
-
const failedChecks: string[] = [];
|
|
410
|
-
const warnings: string[] = [];
|
|
411
|
-
for (const item of items) {
|
|
412
|
-
if (item.success === false) {
|
|
413
|
-
failedChecks.push(`${getCommandNameFromBatchItem(item) ?? "step"} failed`);
|
|
414
|
-
}
|
|
415
|
-
const result = isRecord(item.result) ? item.result : undefined;
|
|
416
|
-
const commandName = getCommandNameFromBatchItem(item);
|
|
417
|
-
if (commandName === "errors" && Array.isArray(result?.errors) && result.errors.length > 0) {
|
|
418
|
-
failedChecks.push(`${result.errors.length} page error(s)`);
|
|
419
|
-
}
|
|
420
|
-
if (commandName === "console" && Array.isArray(result?.messages)) {
|
|
421
|
-
const errorCount = result.messages.filter((message) => isRecord(message) && /error/i.test(String(message.type ?? message.level ?? ""))).length;
|
|
422
|
-
if (errorCount > 0) failedChecks.push(`${errorCount} console error message(s)`);
|
|
423
|
-
}
|
|
424
|
-
if (commandName === "network" && Array.isArray(result?.requests)) {
|
|
425
|
-
const networkFailures = summarizeNetworkFailures(result.requests);
|
|
426
|
-
if (networkFailures.actionableCount > 0) failedChecks.push(`${networkFailures.actionableCount} actionable failed network request(s)`);
|
|
427
|
-
if (networkFailures.benignCount > 0) warnings.push(`${networkFailures.benignCount} benign network request failure(s) ignored`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
if (compiled?.checks.expectedText.length) {
|
|
431
|
-
let expectedTextIndex = 0;
|
|
432
|
-
compiled.steps.forEach((step, index) => {
|
|
433
|
-
if (step.action !== "assertText") return;
|
|
434
|
-
const expected = compiled.checks.expectedText[expectedTextIndex++];
|
|
435
|
-
if (!expected) return;
|
|
436
|
-
const visibleTextPassed = qaVisibleTextWaitPassed(items[index], step);
|
|
437
|
-
if (visibleTextPassed === true) return;
|
|
438
|
-
const actual = extractQaTextAssertionResultText(items[index]);
|
|
439
|
-
if (!actual || !actual.includes(expected)) failedChecks.push(`expected text not found: ${formatQaExpectedTextPreview(expected)}`);
|
|
440
|
-
});
|
|
441
|
-
}
|
|
442
|
-
const uniqueFailures = [...new Set(failedChecks)];
|
|
443
|
-
const uniqueWarnings = [...new Set(warnings)];
|
|
444
|
-
return {
|
|
445
|
-
failedChecks: uniqueFailures,
|
|
446
|
-
passed: uniqueFailures.length === 0,
|
|
447
|
-
summary: uniqueFailures.length === 0
|
|
448
|
-
? uniqueWarnings.length === 0 ? "QA preset passed." : `QA preset passed with warnings: ${uniqueWarnings.join("; ")}.`
|
|
449
|
-
: `QA preset failed: ${uniqueFailures.join("; ")}.`,
|
|
450
|
-
warnings: uniqueWarnings,
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
export function compileAgentBrowserQaPreset(input: unknown): { compiled?: CompiledAgentBrowserQaPreset; error?: string } {
|
|
455
|
-
if (!isRecord(input)) {
|
|
456
|
-
return { error: "qa must be an object." };
|
|
457
|
-
}
|
|
458
|
-
const attached = input.attached === true;
|
|
459
|
-
if (input.attached !== undefined && typeof input.attached !== "boolean") {
|
|
460
|
-
return { error: "qa.attached must be a boolean when provided." };
|
|
461
|
-
}
|
|
462
|
-
const url = input.url;
|
|
463
|
-
if (attached && url !== undefined) {
|
|
464
|
-
return { error: "qa.url must be omitted when qa.attached is true." };
|
|
465
|
-
}
|
|
466
|
-
if (!attached && (typeof url !== "string" || url.trim().length === 0)) {
|
|
467
|
-
return { error: "qa.url must be a non-empty string." };
|
|
468
|
-
}
|
|
469
|
-
const normalizedUrl = typeof url === "string" ? url.trim() : undefined;
|
|
470
|
-
const expectedText = input.expectedText === undefined
|
|
471
|
-
? []
|
|
472
|
-
: typeof input.expectedText === "string"
|
|
473
|
-
? [input.expectedText]
|
|
474
|
-
: Array.isArray(input.expectedText)
|
|
475
|
-
? input.expectedText
|
|
476
|
-
: undefined;
|
|
477
|
-
if (!expectedText || expectedText.some((text) => typeof text !== "string" || text.trim().length === 0)) {
|
|
478
|
-
return { error: "qa.expectedText must be a non-empty string or array of non-empty strings when provided." };
|
|
479
|
-
}
|
|
480
|
-
const expectedSelector = input.expectedSelector;
|
|
481
|
-
if (expectedSelector !== undefined && (typeof expectedSelector !== "string" || expectedSelector.trim().length === 0)) {
|
|
482
|
-
return { error: "qa.expectedSelector must be a non-empty string when provided." };
|
|
483
|
-
}
|
|
484
|
-
const screenshotPath = input.screenshotPath;
|
|
485
|
-
if (screenshotPath !== undefined && (typeof screenshotPath !== "string" || screenshotPath.trim().length === 0)) {
|
|
486
|
-
return { error: "qa.screenshotPath must be a non-empty string when provided." };
|
|
487
|
-
}
|
|
488
|
-
for (const field of ["checkConsole", "checkErrors", "checkNetwork"] as const) {
|
|
489
|
-
if (input[field] !== undefined && typeof input[field] !== "boolean") {
|
|
490
|
-
return { error: `qa.${field} must be a boolean when provided.` };
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
const rawLoadState = input.loadState;
|
|
494
|
-
if (rawLoadState !== undefined && (typeof rawLoadState !== "string" || !AGENT_BROWSER_QA_LOAD_STATES.includes(rawLoadState as AgentBrowserQaLoadState))) {
|
|
495
|
-
return { error: `qa.loadState must be one of: ${AGENT_BROWSER_QA_LOAD_STATES.join(", ")}.` };
|
|
496
|
-
}
|
|
497
|
-
const checkConsole = typeof input.checkConsole === "boolean" ? input.checkConsole : !attached;
|
|
498
|
-
const checkErrors = typeof input.checkErrors === "boolean" ? input.checkErrors : !attached;
|
|
499
|
-
const checkNetwork = typeof input.checkNetwork === "boolean" ? input.checkNetwork : !attached;
|
|
500
|
-
const loadState = (rawLoadState as AgentBrowserQaLoadState | undefined) ?? "domcontentloaded";
|
|
501
|
-
const diagnosticsResetAtStart = !attached;
|
|
502
|
-
const steps: CompiledAgentBrowserJobStep[] = [];
|
|
503
|
-
if (diagnosticsResetAtStart && checkNetwork) steps.push({ action: "wait", args: ["network", "requests", "--clear"] });
|
|
504
|
-
if (diagnosticsResetAtStart && checkConsole) steps.push({ action: "wait", args: ["console", "--clear"] });
|
|
505
|
-
if (diagnosticsResetAtStart && checkErrors) steps.push({ action: "wait", args: ["errors", "--clear"] });
|
|
506
|
-
if (!attached && normalizedUrl) steps.push({ action: "open", args: ["open", normalizedUrl] });
|
|
507
|
-
steps.push({ action: "wait", args: ["wait", "--load", loadState] });
|
|
508
|
-
for (const text of expectedText) {
|
|
509
|
-
steps.push({ action: "assertText", args: ["wait", "--fn", buildQaVisibleTextPredicate(text), "--timeout", String(QA_VISIBLE_TEXT_TIMEOUT_MS)] });
|
|
510
|
-
}
|
|
511
|
-
if (typeof expectedSelector === "string") {
|
|
512
|
-
steps.push({ action: "wait", args: ["wait", expectedSelector] });
|
|
513
|
-
}
|
|
514
|
-
if (checkNetwork) steps.push({ action: "wait", args: ["network", "requests"] });
|
|
515
|
-
if (checkConsole) steps.push({ action: "wait", args: ["console"] });
|
|
516
|
-
if (checkErrors) steps.push({ action: "wait", args: ["errors"] });
|
|
517
|
-
if (typeof screenshotPath === "string") steps.push({ action: "screenshot", args: ["screenshot", screenshotPath] });
|
|
518
|
-
return {
|
|
519
|
-
compiled: {
|
|
520
|
-
args: ["batch", "--bail"],
|
|
521
|
-
checks: { attached, checkConsole, checkErrors, checkNetwork, diagnosticsResetAtStart, expectedSelector, expectedText, loadState, screenshotPath, url: normalizedUrl },
|
|
522
|
-
failFast: true,
|
|
523
|
-
stdin: JSON.stringify(steps.map((step) => step.args)),
|
|
524
|
-
steps,
|
|
525
|
-
},
|
|
526
|
-
};
|
|
527
|
-
}
|