opensteer 0.4.14 → 0.5.0
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/{chunk-WXUXG76V.js → chunk-SXPIGCSD.js} +746 -899
- package/dist/cli/server.cjs +777 -929
- package/dist/cli/server.js +1 -1
- package/dist/index.cjs +777 -931
- package/dist/index.d.cts +16 -29
- package/dist/index.d.ts +16 -29
- package/dist/index.js +1 -3
- package/package.json +1 -1
|
@@ -1192,9 +1192,6 @@ var ENSURE_NAME_SHIM_SCRIPT = `
|
|
|
1192
1192
|
`;
|
|
1193
1193
|
var OS_FRAME_TOKEN_KEY = "__opensteerFrameToken";
|
|
1194
1194
|
var OS_INSTANCE_TOKEN_KEY = "__opensteerInstanceToken";
|
|
1195
|
-
var OS_COUNTER_OWNER_KEY = "__opensteerCounterOwner";
|
|
1196
|
-
var OS_COUNTER_VALUE_KEY = "__opensteerCounterValue";
|
|
1197
|
-
var OS_COUNTER_NEXT_KEY = "__opensteerCounterNext";
|
|
1198
1195
|
|
|
1199
1196
|
// src/element-path/build.ts
|
|
1200
1197
|
var MAX_ATTRIBUTE_VALUE_LENGTH = 300;
|
|
@@ -2985,567 +2982,179 @@ function cleanForAction(html) {
|
|
|
2985
2982
|
return compactHtml(htmlOut);
|
|
2986
2983
|
}
|
|
2987
2984
|
|
|
2988
|
-
// src/
|
|
2989
|
-
|
|
2990
|
-
function
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
return text || null;
|
|
3004
|
-
}
|
|
3005
|
-
function pickSingleListAttributeValue(attribute, raw) {
|
|
3006
|
-
if (attribute === "ping") {
|
|
3007
|
-
const firstUrl = raw.trim().split(/\s+/)[0] || "";
|
|
3008
|
-
return firstUrl.trim();
|
|
3009
|
-
}
|
|
3010
|
-
if (attribute === "srcset" || attribute === "imagesrcset") {
|
|
3011
|
-
const picked = pickBestSrcsetCandidate(raw);
|
|
3012
|
-
if (picked) return picked;
|
|
3013
|
-
return pickFirstSrcsetToken(raw) || "";
|
|
3014
|
-
}
|
|
3015
|
-
return raw.trim();
|
|
3016
|
-
}
|
|
3017
|
-
function pickBestSrcsetCandidate(raw) {
|
|
3018
|
-
const candidates = parseSrcsetCandidates(raw);
|
|
3019
|
-
if (!candidates.length) return null;
|
|
3020
|
-
const widthCandidates = candidates.filter(
|
|
3021
|
-
(candidate) => typeof candidate.width === "number" && Number.isFinite(candidate.width) && candidate.width > 0
|
|
3022
|
-
);
|
|
3023
|
-
if (widthCandidates.length) {
|
|
3024
|
-
return widthCandidates.reduce(
|
|
3025
|
-
(best, candidate) => candidate.width > best.width ? candidate : best
|
|
3026
|
-
).url;
|
|
3027
|
-
}
|
|
3028
|
-
const densityCandidates = candidates.filter(
|
|
3029
|
-
(candidate) => typeof candidate.density === "number" && Number.isFinite(candidate.density) && candidate.density > 0
|
|
3030
|
-
);
|
|
3031
|
-
if (densityCandidates.length) {
|
|
3032
|
-
return densityCandidates.reduce(
|
|
3033
|
-
(best, candidate) => candidate.density > best.density ? candidate : best
|
|
3034
|
-
).url;
|
|
3035
|
-
}
|
|
3036
|
-
return candidates[0]?.url || null;
|
|
3037
|
-
}
|
|
3038
|
-
function parseSrcsetCandidates(raw) {
|
|
3039
|
-
const text = String(raw || "").trim();
|
|
3040
|
-
if (!text) return [];
|
|
3041
|
-
const out = [];
|
|
3042
|
-
let index = 0;
|
|
3043
|
-
while (index < text.length) {
|
|
3044
|
-
index = skipSeparators(text, index);
|
|
3045
|
-
if (index >= text.length) break;
|
|
3046
|
-
const urlToken = readUrlToken(text, index);
|
|
3047
|
-
index = urlToken.nextIndex;
|
|
3048
|
-
const url = urlToken.value.trim();
|
|
3049
|
-
if (!url) continue;
|
|
3050
|
-
index = skipWhitespace(text, index);
|
|
3051
|
-
const descriptors = [];
|
|
3052
|
-
while (index < text.length && text[index] !== ",") {
|
|
3053
|
-
const descriptorToken = readDescriptorToken(text, index);
|
|
3054
|
-
if (!descriptorToken.value) {
|
|
3055
|
-
index = descriptorToken.nextIndex;
|
|
3056
|
-
continue;
|
|
3057
|
-
}
|
|
3058
|
-
descriptors.push(descriptorToken.value);
|
|
3059
|
-
index = descriptorToken.nextIndex;
|
|
3060
|
-
index = skipWhitespace(text, index);
|
|
3061
|
-
}
|
|
3062
|
-
if (index < text.length && text[index] === ",") {
|
|
3063
|
-
index += 1;
|
|
3064
|
-
}
|
|
3065
|
-
let width = null;
|
|
3066
|
-
let density = null;
|
|
3067
|
-
for (const descriptor of descriptors) {
|
|
3068
|
-
const token = descriptor.trim().toLowerCase();
|
|
3069
|
-
if (!token) continue;
|
|
3070
|
-
const widthMatch = token.match(/^(\d+)w$/);
|
|
3071
|
-
if (widthMatch) {
|
|
3072
|
-
const parsed = Number.parseInt(widthMatch[1], 10);
|
|
3073
|
-
if (Number.isFinite(parsed)) {
|
|
3074
|
-
width = parsed;
|
|
3075
|
-
}
|
|
3076
|
-
continue;
|
|
3077
|
-
}
|
|
3078
|
-
const densityMatch = token.match(/^(\d*\.?\d+)x$/);
|
|
3079
|
-
if (densityMatch) {
|
|
3080
|
-
const parsed = Number.parseFloat(densityMatch[1]);
|
|
3081
|
-
if (Number.isFinite(parsed)) {
|
|
3082
|
-
density = parsed;
|
|
3083
|
-
}
|
|
3084
|
-
}
|
|
3085
|
-
}
|
|
3086
|
-
out.push({
|
|
3087
|
-
url,
|
|
3088
|
-
width,
|
|
3089
|
-
density
|
|
3090
|
-
});
|
|
3091
|
-
}
|
|
3092
|
-
return out;
|
|
3093
|
-
}
|
|
3094
|
-
function pickFirstSrcsetToken(raw) {
|
|
3095
|
-
const candidate = parseSrcsetCandidates(raw)[0];
|
|
3096
|
-
if (candidate?.url) {
|
|
3097
|
-
return candidate.url;
|
|
3098
|
-
}
|
|
3099
|
-
const text = String(raw || "");
|
|
3100
|
-
const start = skipSeparators(text, 0);
|
|
3101
|
-
if (start >= text.length) return null;
|
|
3102
|
-
const firstToken = readUrlToken(text, start).value.trim();
|
|
3103
|
-
return firstToken || null;
|
|
3104
|
-
}
|
|
3105
|
-
function skipWhitespace(value, index) {
|
|
3106
|
-
let cursor = index;
|
|
3107
|
-
while (cursor < value.length && /\s/.test(value[cursor])) {
|
|
3108
|
-
cursor += 1;
|
|
3109
|
-
}
|
|
3110
|
-
return cursor;
|
|
3111
|
-
}
|
|
3112
|
-
function skipSeparators(value, index) {
|
|
3113
|
-
let cursor = skipWhitespace(value, index);
|
|
3114
|
-
while (cursor < value.length && value[cursor] === ",") {
|
|
3115
|
-
cursor += 1;
|
|
3116
|
-
cursor = skipWhitespace(value, cursor);
|
|
3117
|
-
}
|
|
3118
|
-
return cursor;
|
|
3119
|
-
}
|
|
3120
|
-
function readUrlToken(value, index) {
|
|
3121
|
-
let cursor = index;
|
|
3122
|
-
let out = "";
|
|
3123
|
-
const isDataUrl = value.slice(index, index + 5).toLowerCase().startsWith("data:");
|
|
3124
|
-
while (cursor < value.length) {
|
|
3125
|
-
const char = value[cursor];
|
|
3126
|
-
if (/\s/.test(char)) {
|
|
3127
|
-
break;
|
|
3128
|
-
}
|
|
3129
|
-
if (char === "," && !isDataUrl) {
|
|
3130
|
-
break;
|
|
3131
|
-
}
|
|
3132
|
-
out += char;
|
|
3133
|
-
cursor += 1;
|
|
3134
|
-
}
|
|
3135
|
-
if (isDataUrl && out.endsWith(",") && cursor < value.length) {
|
|
3136
|
-
out = out.slice(0, -1);
|
|
2985
|
+
// src/html/pipeline.ts
|
|
2986
|
+
import * as cheerio3 from "cheerio";
|
|
2987
|
+
function applyCleaner(mode, html) {
|
|
2988
|
+
switch (mode) {
|
|
2989
|
+
case "clickable":
|
|
2990
|
+
return cleanForClickable(html);
|
|
2991
|
+
case "scrollable":
|
|
2992
|
+
return cleanForScrollable(html);
|
|
2993
|
+
case "extraction":
|
|
2994
|
+
return cleanForExtraction(html);
|
|
2995
|
+
case "full":
|
|
2996
|
+
return cleanForFull(html);
|
|
2997
|
+
case "action":
|
|
2998
|
+
default:
|
|
2999
|
+
return cleanForAction(html);
|
|
3137
3000
|
}
|
|
3138
|
-
return {
|
|
3139
|
-
value: out,
|
|
3140
|
-
nextIndex: cursor
|
|
3141
|
-
};
|
|
3142
3001
|
}
|
|
3143
|
-
function
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3002
|
+
async function assignCounters(page, html, nodePaths, nodeMeta) {
|
|
3003
|
+
const $ = cheerio3.load(html, { xmlMode: false });
|
|
3004
|
+
const counterIndex = /* @__PURE__ */ new Map();
|
|
3005
|
+
let nextCounter = 1;
|
|
3006
|
+
const assignedByNodeId = /* @__PURE__ */ new Map();
|
|
3007
|
+
$("*").each(function() {
|
|
3008
|
+
const el = $(this);
|
|
3009
|
+
const nodeId = el.attr(OS_NODE_ID_ATTR);
|
|
3010
|
+
if (!nodeId) return;
|
|
3011
|
+
const counter = nextCounter++;
|
|
3012
|
+
assignedByNodeId.set(nodeId, counter);
|
|
3013
|
+
const path5 = nodePaths.get(nodeId);
|
|
3014
|
+
el.attr("c", String(counter));
|
|
3015
|
+
el.removeAttr(OS_NODE_ID_ATTR);
|
|
3016
|
+
if (path5) {
|
|
3017
|
+
counterIndex.set(counter, cloneElementPath(path5));
|
|
3150
3018
|
}
|
|
3151
|
-
|
|
3152
|
-
|
|
3019
|
+
});
|
|
3020
|
+
try {
|
|
3021
|
+
await syncLiveCounters(page, nodeMeta, assignedByNodeId);
|
|
3022
|
+
} catch (error) {
|
|
3023
|
+
await clearLiveCounters(page);
|
|
3024
|
+
throw error;
|
|
3153
3025
|
}
|
|
3026
|
+
$(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
|
|
3154
3027
|
return {
|
|
3155
|
-
|
|
3156
|
-
|
|
3028
|
+
html: $.html(),
|
|
3029
|
+
counterIndex
|
|
3157
3030
|
};
|
|
3158
3031
|
}
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
super(message);
|
|
3165
|
-
this.name = "CounterResolutionError";
|
|
3166
|
-
this.code = code;
|
|
3167
|
-
}
|
|
3168
|
-
};
|
|
3169
|
-
async function ensureLiveCounters(page, nodeMeta, nodeIds) {
|
|
3170
|
-
const out = /* @__PURE__ */ new Map();
|
|
3171
|
-
if (!nodeIds.length) return out;
|
|
3172
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
3173
|
-
for (const nodeId of nodeIds) {
|
|
3032
|
+
async function syncLiveCounters(page, nodeMeta, assignedByNodeId) {
|
|
3033
|
+
await clearLiveCounters(page);
|
|
3034
|
+
if (!assignedByNodeId.size) return;
|
|
3035
|
+
const groupedByFrame = /* @__PURE__ */ new Map();
|
|
3036
|
+
for (const [nodeId, counter] of assignedByNodeId.entries()) {
|
|
3174
3037
|
const meta = nodeMeta.get(nodeId);
|
|
3175
|
-
if (!meta)
|
|
3176
|
-
|
|
3177
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
3178
|
-
`Missing metadata for node ${nodeId}. Run snapshot() again.`
|
|
3179
|
-
);
|
|
3180
|
-
}
|
|
3181
|
-
const list = grouped.get(meta.frameToken) || [];
|
|
3038
|
+
if (!meta?.frameToken) continue;
|
|
3039
|
+
const list = groupedByFrame.get(meta.frameToken) || [];
|
|
3182
3040
|
list.push({
|
|
3183
3041
|
nodeId,
|
|
3184
|
-
|
|
3042
|
+
counter
|
|
3185
3043
|
});
|
|
3186
|
-
|
|
3044
|
+
groupedByFrame.set(meta.frameToken, list);
|
|
3187
3045
|
}
|
|
3046
|
+
if (!groupedByFrame.size) return;
|
|
3047
|
+
const failures = [];
|
|
3188
3048
|
const framesByToken = await mapFramesByToken(page);
|
|
3189
|
-
|
|
3190
|
-
const usedCounters = /* @__PURE__ */ new Map();
|
|
3191
|
-
for (const [frameToken, entries] of grouped.entries()) {
|
|
3049
|
+
for (const [frameToken, entries] of groupedByFrame.entries()) {
|
|
3192
3050
|
const frame = framesByToken.get(frameToken);
|
|
3193
3051
|
if (!frame) {
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3052
|
+
for (const entry of entries) {
|
|
3053
|
+
failures.push({
|
|
3054
|
+
nodeId: entry.nodeId,
|
|
3055
|
+
counter: entry.counter,
|
|
3056
|
+
frameToken,
|
|
3057
|
+
reason: "frame_missing"
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
continue;
|
|
3198
3061
|
}
|
|
3199
|
-
|
|
3200
|
-
(
|
|
3201
|
-
entries: entries2,
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
counterValueKey,
|
|
3206
|
-
startCounter
|
|
3207
|
-
}) => {
|
|
3208
|
-
const helpers = {
|
|
3209
|
-
pushNode(map, node) {
|
|
3210
|
-
const nodeId = node.getAttribute(nodeAttr);
|
|
3211
|
-
if (!nodeId) return;
|
|
3212
|
-
const list = map.get(nodeId) || [];
|
|
3213
|
-
list.push(node);
|
|
3214
|
-
map.set(nodeId, list);
|
|
3215
|
-
},
|
|
3216
|
-
walk(map, root) {
|
|
3062
|
+
try {
|
|
3063
|
+
const unresolved = await frame.evaluate(
|
|
3064
|
+
({ entries: entries2, nodeAttr }) => {
|
|
3065
|
+
const index = /* @__PURE__ */ new Map();
|
|
3066
|
+
const unresolved2 = [];
|
|
3067
|
+
const walk = (root) => {
|
|
3217
3068
|
const children = Array.from(root.children);
|
|
3218
3069
|
for (const child of children) {
|
|
3219
|
-
|
|
3220
|
-
|
|
3070
|
+
const nodeId = child.getAttribute(nodeAttr);
|
|
3071
|
+
if (nodeId) {
|
|
3072
|
+
const list = index.get(nodeId) || [];
|
|
3073
|
+
list.push(child);
|
|
3074
|
+
index.set(nodeId, list);
|
|
3075
|
+
}
|
|
3076
|
+
walk(child);
|
|
3221
3077
|
if (child.shadowRoot) {
|
|
3222
|
-
|
|
3078
|
+
walk(child.shadowRoot);
|
|
3223
3079
|
}
|
|
3224
3080
|
}
|
|
3225
|
-
}
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
if (!matches.length) {
|
|
3239
|
-
failures.push({
|
|
3240
|
-
nodeId: entry.nodeId,
|
|
3241
|
-
reason: "missing"
|
|
3242
|
-
});
|
|
3243
|
-
continue;
|
|
3244
|
-
}
|
|
3245
|
-
if (matches.length !== 1) {
|
|
3246
|
-
failures.push({
|
|
3247
|
-
nodeId: entry.nodeId,
|
|
3248
|
-
reason: "ambiguous"
|
|
3249
|
-
});
|
|
3250
|
-
continue;
|
|
3251
|
-
}
|
|
3252
|
-
const target = matches[0];
|
|
3253
|
-
if (target[instanceTokenKey] !== entry.instanceToken) {
|
|
3254
|
-
failures.push({
|
|
3255
|
-
nodeId: entry.nodeId,
|
|
3256
|
-
reason: "instance_mismatch"
|
|
3257
|
-
});
|
|
3258
|
-
continue;
|
|
3259
|
-
}
|
|
3260
|
-
const owned = target[counterOwnerKey] === true;
|
|
3261
|
-
const runtimeCounter = Number(target[counterValueKey] || 0);
|
|
3262
|
-
if (owned && Number.isFinite(runtimeCounter) && runtimeCounter > 0) {
|
|
3263
|
-
target.setAttribute("c", String(runtimeCounter));
|
|
3264
|
-
assigned.push({
|
|
3265
|
-
nodeId: entry.nodeId,
|
|
3266
|
-
counter: runtimeCounter
|
|
3267
|
-
});
|
|
3268
|
-
continue;
|
|
3081
|
+
};
|
|
3082
|
+
walk(document);
|
|
3083
|
+
for (const entry of entries2) {
|
|
3084
|
+
const matches = index.get(entry.nodeId) || [];
|
|
3085
|
+
if (matches.length !== 1) {
|
|
3086
|
+
unresolved2.push({
|
|
3087
|
+
nodeId: entry.nodeId,
|
|
3088
|
+
counter: entry.counter,
|
|
3089
|
+
matches: matches.length
|
|
3090
|
+
});
|
|
3091
|
+
continue;
|
|
3092
|
+
}
|
|
3093
|
+
matches[0].setAttribute("c", String(entry.counter));
|
|
3269
3094
|
}
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
configurable: true
|
|
3276
|
-
});
|
|
3277
|
-
Object.defineProperty(target, counterValueKey, {
|
|
3278
|
-
value: counter,
|
|
3279
|
-
writable: true,
|
|
3280
|
-
configurable: true
|
|
3281
|
-
});
|
|
3282
|
-
assigned.push({ nodeId: entry.nodeId, counter });
|
|
3095
|
+
return unresolved2;
|
|
3096
|
+
},
|
|
3097
|
+
{
|
|
3098
|
+
entries,
|
|
3099
|
+
nodeAttr: OS_NODE_ID_ATTR
|
|
3283
3100
|
}
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
}
|
|
3303
|
-
nextCounter = result.nextCounter;
|
|
3304
|
-
for (const item of result.assigned) {
|
|
3305
|
-
const existingNode = usedCounters.get(item.counter);
|
|
3306
|
-
if (existingNode && existingNode !== item.nodeId) {
|
|
3307
|
-
throw new CounterResolutionError(
|
|
3308
|
-
"ERR_COUNTER_AMBIGUOUS",
|
|
3309
|
-
`Counter ${item.counter} is assigned to multiple nodes (${existingNode}, ${item.nodeId}). Run snapshot() again.`
|
|
3310
|
-
);
|
|
3101
|
+
);
|
|
3102
|
+
for (const entry of unresolved) {
|
|
3103
|
+
failures.push({
|
|
3104
|
+
nodeId: entry.nodeId,
|
|
3105
|
+
counter: entry.counter,
|
|
3106
|
+
frameToken,
|
|
3107
|
+
reason: "match_count",
|
|
3108
|
+
matches: entry.matches
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
3111
|
+
} catch {
|
|
3112
|
+
for (const entry of entries) {
|
|
3113
|
+
failures.push({
|
|
3114
|
+
nodeId: entry.nodeId,
|
|
3115
|
+
counter: entry.counter,
|
|
3116
|
+
frameToken,
|
|
3117
|
+
reason: "frame_unavailable"
|
|
3118
|
+
});
|
|
3311
3119
|
}
|
|
3312
|
-
usedCounters.set(item.counter, item.nodeId);
|
|
3313
|
-
out.set(item.nodeId, item.counter);
|
|
3314
3120
|
}
|
|
3315
3121
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
const framesByToken = await mapFramesByToken(page);
|
|
3322
|
-
const frame = framesByToken.get(binding.frameToken);
|
|
3323
|
-
if (!frame) {
|
|
3324
|
-
throw new CounterResolutionError(
|
|
3325
|
-
"ERR_COUNTER_FRAME_UNAVAILABLE",
|
|
3326
|
-
`Counter ${counter} frame is unavailable. Run snapshot() again.`
|
|
3327
|
-
);
|
|
3328
|
-
}
|
|
3329
|
-
const status = await frame.evaluate(
|
|
3330
|
-
({
|
|
3331
|
-
nodeId,
|
|
3332
|
-
instanceToken,
|
|
3333
|
-
counter: counter2,
|
|
3334
|
-
nodeAttr,
|
|
3335
|
-
instanceTokenKey,
|
|
3336
|
-
counterOwnerKey,
|
|
3337
|
-
counterValueKey
|
|
3338
|
-
}) => {
|
|
3339
|
-
const helpers = {
|
|
3340
|
-
walk(map, root) {
|
|
3341
|
-
const children = Array.from(root.children);
|
|
3342
|
-
for (const child of children) {
|
|
3343
|
-
const id = child.getAttribute(nodeAttr);
|
|
3344
|
-
if (id) {
|
|
3345
|
-
const list = map.get(id) || [];
|
|
3346
|
-
list.push(child);
|
|
3347
|
-
map.set(id, list);
|
|
3348
|
-
}
|
|
3349
|
-
helpers.walk(map, child);
|
|
3350
|
-
if (child.shadowRoot) {
|
|
3351
|
-
helpers.walk(map, child.shadowRoot);
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
|
-
},
|
|
3355
|
-
buildNodeIndex() {
|
|
3356
|
-
const map = /* @__PURE__ */ new Map();
|
|
3357
|
-
helpers.walk(map, document);
|
|
3358
|
-
return map;
|
|
3359
|
-
}
|
|
3360
|
-
};
|
|
3361
|
-
const matches = helpers.buildNodeIndex().get(nodeId) || [];
|
|
3362
|
-
if (!matches.length) return "missing";
|
|
3363
|
-
if (matches.length !== 1) return "ambiguous";
|
|
3364
|
-
const target = matches[0];
|
|
3365
|
-
if (target[instanceTokenKey] !== instanceToken) {
|
|
3366
|
-
return "instance_mismatch";
|
|
3367
|
-
}
|
|
3368
|
-
if (target[counterOwnerKey] !== true) {
|
|
3369
|
-
return "instance_mismatch";
|
|
3122
|
+
if (failures.length) {
|
|
3123
|
+
const preview = failures.slice(0, 3).map((failure) => {
|
|
3124
|
+
const base = `counter ${failure.counter} (nodeId "${failure.nodeId}") in frame "${failure.frameToken}"`;
|
|
3125
|
+
if (failure.reason === "frame_missing") {
|
|
3126
|
+
return `${base} could not be synchronized because the frame is missing.`;
|
|
3370
3127
|
}
|
|
3371
|
-
if (
|
|
3372
|
-
return
|
|
3128
|
+
if (failure.reason === "frame_unavailable") {
|
|
3129
|
+
return `${base} could not be synchronized because frame evaluation failed.`;
|
|
3373
3130
|
}
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
nodeId: binding.nodeId,
|
|
3381
|
-
instanceToken: binding.instanceToken,
|
|
3382
|
-
counter,
|
|
3383
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
3384
|
-
instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
|
|
3385
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
3386
|
-
counterValueKey: OS_COUNTER_VALUE_KEY
|
|
3387
|
-
}
|
|
3388
|
-
);
|
|
3389
|
-
if (status !== "ok") {
|
|
3390
|
-
throw buildCounterFailureError(binding.nodeId, status);
|
|
3131
|
+
return `${base} expected exactly one live node but found ${failure.matches ?? 0}.`;
|
|
3132
|
+
});
|
|
3133
|
+
const remaining = failures.length > 3 ? ` (+${failures.length - 3} more)` : "";
|
|
3134
|
+
throw new Error(
|
|
3135
|
+
`Failed to synchronize snapshot counters with the live DOM: ${preview.join(" ")}${remaining}`
|
|
3136
|
+
);
|
|
3391
3137
|
}
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3138
|
+
}
|
|
3139
|
+
async function clearLiveCounters(page) {
|
|
3140
|
+
for (const frame of page.frames()) {
|
|
3141
|
+
try {
|
|
3142
|
+
await frame.evaluate(() => {
|
|
3143
|
+
const walk = (root) => {
|
|
3396
3144
|
const children = Array.from(root.children);
|
|
3397
3145
|
for (const child of children) {
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
}
|
|
3401
|
-
helpers.walk(matches, child);
|
|
3146
|
+
child.removeAttribute("c");
|
|
3147
|
+
walk(child);
|
|
3402
3148
|
if (child.shadowRoot) {
|
|
3403
|
-
|
|
3149
|
+
walk(child.shadowRoot);
|
|
3404
3150
|
}
|
|
3405
3151
|
}
|
|
3406
|
-
},
|
|
3407
|
-
findUniqueNode() {
|
|
3408
|
-
const matches = [];
|
|
3409
|
-
helpers.walk(matches, document);
|
|
3410
|
-
if (matches.length !== 1) return null;
|
|
3411
|
-
return matches[0];
|
|
3412
|
-
}
|
|
3413
|
-
};
|
|
3414
|
-
return helpers.findUniqueNode();
|
|
3415
|
-
},
|
|
3416
|
-
{
|
|
3417
|
-
nodeId: binding.nodeId,
|
|
3418
|
-
nodeAttr: OS_NODE_ID_ATTR
|
|
3419
|
-
}
|
|
3420
|
-
);
|
|
3421
|
-
const element = handle.asElement();
|
|
3422
|
-
if (!element) {
|
|
3423
|
-
await handle.dispose();
|
|
3424
|
-
throw new CounterResolutionError(
|
|
3425
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
3426
|
-
`Counter ${counter} became stale. Run snapshot() again.`
|
|
3427
|
-
);
|
|
3428
|
-
}
|
|
3429
|
-
return element;
|
|
3430
|
-
}
|
|
3431
|
-
async function resolveCountersBatch(page, snapshot, requests) {
|
|
3432
|
-
const out = {};
|
|
3433
|
-
if (!requests.length) return out;
|
|
3434
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
3435
|
-
for (const request of requests) {
|
|
3436
|
-
const binding = readBinding(snapshot, request.counter);
|
|
3437
|
-
const list = grouped.get(binding.frameToken) || [];
|
|
3438
|
-
list.push({
|
|
3439
|
-
...request,
|
|
3440
|
-
...binding
|
|
3441
|
-
});
|
|
3442
|
-
grouped.set(binding.frameToken, list);
|
|
3443
|
-
}
|
|
3444
|
-
const framesByToken = await mapFramesByToken(page);
|
|
3445
|
-
for (const [frameToken, entries] of grouped.entries()) {
|
|
3446
|
-
const frame = framesByToken.get(frameToken);
|
|
3447
|
-
if (!frame) {
|
|
3448
|
-
throw new CounterResolutionError(
|
|
3449
|
-
"ERR_COUNTER_FRAME_UNAVAILABLE",
|
|
3450
|
-
`Counter frame ${frameToken} is unavailable. Run snapshot() again.`
|
|
3451
|
-
);
|
|
3452
|
-
}
|
|
3453
|
-
const result = await frame.evaluate(
|
|
3454
|
-
({
|
|
3455
|
-
entries: entries2,
|
|
3456
|
-
nodeAttr,
|
|
3457
|
-
instanceTokenKey,
|
|
3458
|
-
counterOwnerKey,
|
|
3459
|
-
counterValueKey
|
|
3460
|
-
}) => {
|
|
3461
|
-
const values = [];
|
|
3462
|
-
const failures = [];
|
|
3463
|
-
const helpers = {
|
|
3464
|
-
walk(map, root) {
|
|
3465
|
-
const children = Array.from(root.children);
|
|
3466
|
-
for (const child of children) {
|
|
3467
|
-
const id = child.getAttribute(nodeAttr);
|
|
3468
|
-
if (id) {
|
|
3469
|
-
const list = map.get(id) || [];
|
|
3470
|
-
list.push(child);
|
|
3471
|
-
map.set(id, list);
|
|
3472
|
-
}
|
|
3473
|
-
helpers.walk(map, child);
|
|
3474
|
-
if (child.shadowRoot) {
|
|
3475
|
-
helpers.walk(map, child.shadowRoot);
|
|
3476
|
-
}
|
|
3477
|
-
}
|
|
3478
|
-
},
|
|
3479
|
-
buildNodeIndex() {
|
|
3480
|
-
const map = /* @__PURE__ */ new Map();
|
|
3481
|
-
helpers.walk(map, document);
|
|
3482
|
-
return map;
|
|
3483
|
-
},
|
|
3484
|
-
readRawValue(element, attribute) {
|
|
3485
|
-
if (attribute) {
|
|
3486
|
-
return element.getAttribute(attribute);
|
|
3487
|
-
}
|
|
3488
|
-
return element.textContent;
|
|
3489
|
-
}
|
|
3490
3152
|
};
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
if (!matches.length) {
|
|
3495
|
-
failures.push({
|
|
3496
|
-
nodeId: entry.nodeId,
|
|
3497
|
-
reason: "missing"
|
|
3498
|
-
});
|
|
3499
|
-
continue;
|
|
3500
|
-
}
|
|
3501
|
-
if (matches.length !== 1) {
|
|
3502
|
-
failures.push({
|
|
3503
|
-
nodeId: entry.nodeId,
|
|
3504
|
-
reason: "ambiguous"
|
|
3505
|
-
});
|
|
3506
|
-
continue;
|
|
3507
|
-
}
|
|
3508
|
-
const target = matches[0];
|
|
3509
|
-
if (target[instanceTokenKey] !== entry.instanceToken || target[counterOwnerKey] !== true || Number(target[counterValueKey] || 0) !== entry.counter || target.getAttribute("c") !== String(entry.counter)) {
|
|
3510
|
-
failures.push({
|
|
3511
|
-
nodeId: entry.nodeId,
|
|
3512
|
-
reason: "instance_mismatch"
|
|
3513
|
-
});
|
|
3514
|
-
continue;
|
|
3515
|
-
}
|
|
3516
|
-
values.push({
|
|
3517
|
-
key: entry.key,
|
|
3518
|
-
value: helpers.readRawValue(target, entry.attribute)
|
|
3519
|
-
});
|
|
3520
|
-
}
|
|
3521
|
-
return {
|
|
3522
|
-
values,
|
|
3523
|
-
failures
|
|
3524
|
-
};
|
|
3525
|
-
},
|
|
3526
|
-
{
|
|
3527
|
-
entries,
|
|
3528
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
3529
|
-
instanceTokenKey: OS_INSTANCE_TOKEN_KEY,
|
|
3530
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
3531
|
-
counterValueKey: OS_COUNTER_VALUE_KEY
|
|
3532
|
-
}
|
|
3533
|
-
);
|
|
3534
|
-
if (result.failures.length) {
|
|
3535
|
-
const first = result.failures[0];
|
|
3536
|
-
throw buildCounterFailureError(first.nodeId, first.reason);
|
|
3537
|
-
}
|
|
3538
|
-
const attributeByKey = new Map(
|
|
3539
|
-
entries.map((entry) => [entry.key, entry.attribute])
|
|
3540
|
-
);
|
|
3541
|
-
for (const item of result.values) {
|
|
3542
|
-
out[item.key] = normalizeExtractedValue(
|
|
3543
|
-
item.value,
|
|
3544
|
-
attributeByKey.get(item.key)
|
|
3545
|
-
);
|
|
3153
|
+
walk(document);
|
|
3154
|
+
});
|
|
3155
|
+
} catch {
|
|
3546
3156
|
}
|
|
3547
3157
|
}
|
|
3548
|
-
return out;
|
|
3549
3158
|
}
|
|
3550
3159
|
async function mapFramesByToken(page) {
|
|
3551
3160
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -3567,182 +3176,6 @@ async function readFrameToken(frame) {
|
|
|
3567
3176
|
return null;
|
|
3568
3177
|
}
|
|
3569
3178
|
}
|
|
3570
|
-
async function readGlobalNextCounter(page) {
|
|
3571
|
-
const current = await page.mainFrame().evaluate((counterNextKey) => {
|
|
3572
|
-
const win = window;
|
|
3573
|
-
return Number(win[counterNextKey] || 0);
|
|
3574
|
-
}, OS_COUNTER_NEXT_KEY).catch(() => 0);
|
|
3575
|
-
if (Number.isFinite(current) && current > 0) {
|
|
3576
|
-
return current;
|
|
3577
|
-
}
|
|
3578
|
-
let max = 0;
|
|
3579
|
-
for (const frame of page.frames()) {
|
|
3580
|
-
try {
|
|
3581
|
-
const frameMax = await frame.evaluate(
|
|
3582
|
-
({ nodeAttr, counterOwnerKey, counterValueKey }) => {
|
|
3583
|
-
let localMax = 0;
|
|
3584
|
-
const helpers = {
|
|
3585
|
-
walk(root) {
|
|
3586
|
-
const children = Array.from(
|
|
3587
|
-
root.children
|
|
3588
|
-
);
|
|
3589
|
-
for (const child of children) {
|
|
3590
|
-
const candidate = child;
|
|
3591
|
-
const hasNodeId = child.hasAttribute(nodeAttr);
|
|
3592
|
-
const owned = candidate[counterOwnerKey] === true;
|
|
3593
|
-
if (hasNodeId && owned) {
|
|
3594
|
-
const value = Number(
|
|
3595
|
-
candidate[counterValueKey] || 0
|
|
3596
|
-
);
|
|
3597
|
-
if (Number.isFinite(value) && value > localMax) {
|
|
3598
|
-
localMax = value;
|
|
3599
|
-
}
|
|
3600
|
-
}
|
|
3601
|
-
helpers.walk(child);
|
|
3602
|
-
if (child.shadowRoot) {
|
|
3603
|
-
helpers.walk(child.shadowRoot);
|
|
3604
|
-
}
|
|
3605
|
-
}
|
|
3606
|
-
}
|
|
3607
|
-
};
|
|
3608
|
-
helpers.walk(document);
|
|
3609
|
-
return localMax;
|
|
3610
|
-
},
|
|
3611
|
-
{
|
|
3612
|
-
nodeAttr: OS_NODE_ID_ATTR,
|
|
3613
|
-
counterOwnerKey: OS_COUNTER_OWNER_KEY,
|
|
3614
|
-
counterValueKey: OS_COUNTER_VALUE_KEY
|
|
3615
|
-
}
|
|
3616
|
-
);
|
|
3617
|
-
if (frameMax > max) {
|
|
3618
|
-
max = frameMax;
|
|
3619
|
-
}
|
|
3620
|
-
} catch {
|
|
3621
|
-
}
|
|
3622
|
-
}
|
|
3623
|
-
const next = max + 1;
|
|
3624
|
-
await writeGlobalNextCounter(page, next);
|
|
3625
|
-
return next;
|
|
3626
|
-
}
|
|
3627
|
-
async function writeGlobalNextCounter(page, nextCounter) {
|
|
3628
|
-
await page.mainFrame().evaluate(
|
|
3629
|
-
({ counterNextKey, nextCounter: nextCounter2 }) => {
|
|
3630
|
-
const win = window;
|
|
3631
|
-
win[counterNextKey] = nextCounter2;
|
|
3632
|
-
},
|
|
3633
|
-
{
|
|
3634
|
-
counterNextKey: OS_COUNTER_NEXT_KEY,
|
|
3635
|
-
nextCounter
|
|
3636
|
-
}
|
|
3637
|
-
).catch(() => void 0);
|
|
3638
|
-
}
|
|
3639
|
-
function readBinding(snapshot, counter) {
|
|
3640
|
-
if (!snapshot.counterBindings) {
|
|
3641
|
-
throw new CounterResolutionError(
|
|
3642
|
-
"ERR_COUNTER_NOT_FOUND",
|
|
3643
|
-
`Counter ${counter} is unavailable because this snapshot has no counter bindings. Run snapshot() with counters first.`
|
|
3644
|
-
);
|
|
3645
|
-
}
|
|
3646
|
-
const binding = snapshot.counterBindings.get(counter);
|
|
3647
|
-
if (!binding) {
|
|
3648
|
-
throw new CounterResolutionError(
|
|
3649
|
-
"ERR_COUNTER_NOT_FOUND",
|
|
3650
|
-
`Counter ${counter} was not found in the current snapshot. Run snapshot() again.`
|
|
3651
|
-
);
|
|
3652
|
-
}
|
|
3653
|
-
if (binding.sessionId !== snapshot.snapshotSessionId) {
|
|
3654
|
-
throw new CounterResolutionError(
|
|
3655
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
3656
|
-
`Counter ${counter} is stale for this snapshot session. Run snapshot() again.`
|
|
3657
|
-
);
|
|
3658
|
-
}
|
|
3659
|
-
return binding;
|
|
3660
|
-
}
|
|
3661
|
-
function buildCounterFailureError(nodeId, reason) {
|
|
3662
|
-
if (reason === "ambiguous") {
|
|
3663
|
-
return new CounterResolutionError(
|
|
3664
|
-
"ERR_COUNTER_AMBIGUOUS",
|
|
3665
|
-
`Counter target is ambiguous for node ${nodeId}. Run snapshot() again.`
|
|
3666
|
-
);
|
|
3667
|
-
}
|
|
3668
|
-
return new CounterResolutionError(
|
|
3669
|
-
"ERR_COUNTER_STALE_OR_NOT_FOUND",
|
|
3670
|
-
`Counter target is stale or missing for node ${nodeId}. Run snapshot() again.`
|
|
3671
|
-
);
|
|
3672
|
-
}
|
|
3673
|
-
|
|
3674
|
-
// src/html/pipeline.ts
|
|
3675
|
-
import * as cheerio3 from "cheerio";
|
|
3676
|
-
import { randomUUID } from "crypto";
|
|
3677
|
-
function applyCleaner(mode, html) {
|
|
3678
|
-
switch (mode) {
|
|
3679
|
-
case "clickable":
|
|
3680
|
-
return cleanForClickable(html);
|
|
3681
|
-
case "scrollable":
|
|
3682
|
-
return cleanForScrollable(html);
|
|
3683
|
-
case "extraction":
|
|
3684
|
-
return cleanForExtraction(html);
|
|
3685
|
-
case "full":
|
|
3686
|
-
return cleanForFull(html);
|
|
3687
|
-
case "action":
|
|
3688
|
-
default:
|
|
3689
|
-
return cleanForAction(html);
|
|
3690
|
-
}
|
|
3691
|
-
}
|
|
3692
|
-
async function assignCounters(page, html, nodePaths, nodeMeta, snapshotSessionId) {
|
|
3693
|
-
const $ = cheerio3.load(html, { xmlMode: false });
|
|
3694
|
-
const counterIndex = /* @__PURE__ */ new Map();
|
|
3695
|
-
const counterBindings = /* @__PURE__ */ new Map();
|
|
3696
|
-
const orderedNodeIds = [];
|
|
3697
|
-
$("*").each(function() {
|
|
3698
|
-
const el = $(this);
|
|
3699
|
-
const nodeId = el.attr(OS_NODE_ID_ATTR);
|
|
3700
|
-
if (!nodeId) return;
|
|
3701
|
-
orderedNodeIds.push(nodeId);
|
|
3702
|
-
});
|
|
3703
|
-
const countersByNodeId = await ensureLiveCounters(
|
|
3704
|
-
page,
|
|
3705
|
-
nodeMeta,
|
|
3706
|
-
orderedNodeIds
|
|
3707
|
-
);
|
|
3708
|
-
$("*").each(function() {
|
|
3709
|
-
const el = $(this);
|
|
3710
|
-
const nodeId = el.attr(OS_NODE_ID_ATTR);
|
|
3711
|
-
if (!nodeId) return;
|
|
3712
|
-
const path5 = nodePaths.get(nodeId);
|
|
3713
|
-
const meta = nodeMeta.get(nodeId);
|
|
3714
|
-
const counter = countersByNodeId.get(nodeId);
|
|
3715
|
-
if (counter == null || !Number.isFinite(counter)) {
|
|
3716
|
-
throw new Error(
|
|
3717
|
-
`Counter assignment failed for node ${nodeId}. Run snapshot() again.`
|
|
3718
|
-
);
|
|
3719
|
-
}
|
|
3720
|
-
if (counterBindings.has(counter) && counterBindings.get(counter)?.nodeId !== nodeId) {
|
|
3721
|
-
throw new Error(
|
|
3722
|
-
`Counter ${counter} was assigned to multiple nodes. Run snapshot() again.`
|
|
3723
|
-
);
|
|
3724
|
-
}
|
|
3725
|
-
el.attr("c", String(counter));
|
|
3726
|
-
el.removeAttr(OS_NODE_ID_ATTR);
|
|
3727
|
-
if (path5) {
|
|
3728
|
-
counterIndex.set(counter, cloneElementPath(path5));
|
|
3729
|
-
}
|
|
3730
|
-
if (meta) {
|
|
3731
|
-
counterBindings.set(counter, {
|
|
3732
|
-
sessionId: snapshotSessionId,
|
|
3733
|
-
frameToken: meta.frameToken,
|
|
3734
|
-
nodeId,
|
|
3735
|
-
instanceToken: meta.instanceToken
|
|
3736
|
-
});
|
|
3737
|
-
}
|
|
3738
|
-
});
|
|
3739
|
-
$(`[${OS_NODE_ID_ATTR}]`).removeAttr(OS_NODE_ID_ATTR);
|
|
3740
|
-
return {
|
|
3741
|
-
html: $.html(),
|
|
3742
|
-
counterIndex,
|
|
3743
|
-
counterBindings
|
|
3744
|
-
};
|
|
3745
|
-
}
|
|
3746
3179
|
function stripNodeIds(html) {
|
|
3747
3180
|
if (!html.includes(OS_NODE_ID_ATTR)) return html;
|
|
3748
3181
|
const $ = cheerio3.load(html, { xmlMode: false });
|
|
@@ -3750,7 +3183,6 @@ function stripNodeIds(html) {
|
|
|
3750
3183
|
return $.html();
|
|
3751
3184
|
}
|
|
3752
3185
|
async function prepareSnapshot(page, options = {}) {
|
|
3753
|
-
const snapshotSessionId = randomUUID();
|
|
3754
3186
|
const mode = options.mode ?? "action";
|
|
3755
3187
|
const withCounters = options.withCounters ?? true;
|
|
3756
3188
|
const shouldMarkInteractive = options.markInteractive ?? true;
|
|
@@ -3763,18 +3195,15 @@ async function prepareSnapshot(page, options = {}) {
|
|
|
3763
3195
|
const reducedHtml = applyCleaner(mode, processedHtml);
|
|
3764
3196
|
let cleanedHtml = reducedHtml;
|
|
3765
3197
|
let counterIndex = null;
|
|
3766
|
-
let counterBindings = null;
|
|
3767
3198
|
if (withCounters) {
|
|
3768
3199
|
const counted = await assignCounters(
|
|
3769
3200
|
page,
|
|
3770
3201
|
reducedHtml,
|
|
3771
3202
|
serialized.nodePaths,
|
|
3772
|
-
serialized.nodeMeta
|
|
3773
|
-
snapshotSessionId
|
|
3203
|
+
serialized.nodeMeta
|
|
3774
3204
|
);
|
|
3775
3205
|
cleanedHtml = counted.html;
|
|
3776
3206
|
counterIndex = counted.counterIndex;
|
|
3777
|
-
counterBindings = counted.counterBindings;
|
|
3778
3207
|
} else {
|
|
3779
3208
|
cleanedHtml = stripNodeIds(cleanedHtml);
|
|
3780
3209
|
}
|
|
@@ -3783,15 +3212,13 @@ async function prepareSnapshot(page, options = {}) {
|
|
|
3783
3212
|
cleanedHtml = $unwrap("body").html()?.trim() || cleanedHtml;
|
|
3784
3213
|
}
|
|
3785
3214
|
return {
|
|
3786
|
-
snapshotSessionId,
|
|
3787
3215
|
mode,
|
|
3788
3216
|
url: page.url(),
|
|
3789
3217
|
rawHtml,
|
|
3790
3218
|
processedHtml,
|
|
3791
3219
|
reducedHtml,
|
|
3792
3220
|
cleanedHtml,
|
|
3793
|
-
counterIndex
|
|
3794
|
-
counterBindings
|
|
3221
|
+
counterIndex
|
|
3795
3222
|
};
|
|
3796
3223
|
}
|
|
3797
3224
|
|
|
@@ -3920,111 +3347,512 @@ function buildTargetNotFoundMessage(domPath, diagnostics) {
|
|
|
3920
3347
|
return `${base} Tried ${Math.max(diagnostics.length, 0)} candidates.`;
|
|
3921
3348
|
return `${base} Target depth ${depth}. Candidate counts: ${sample}.`;
|
|
3922
3349
|
}
|
|
3923
|
-
function selectInDocument(selectors) {
|
|
3924
|
-
let fallback = null;
|
|
3925
|
-
for (const selector of selectors) {
|
|
3926
|
-
if (!selector) continue;
|
|
3927
|
-
let count = 0;
|
|
3928
|
-
try {
|
|
3929
|
-
count = document.querySelectorAll(selector).length;
|
|
3930
|
-
} catch {
|
|
3931
|
-
count = 0;
|
|
3350
|
+
function selectInDocument(selectors) {
|
|
3351
|
+
let fallback = null;
|
|
3352
|
+
for (const selector of selectors) {
|
|
3353
|
+
if (!selector) continue;
|
|
3354
|
+
let count = 0;
|
|
3355
|
+
try {
|
|
3356
|
+
count = document.querySelectorAll(selector).length;
|
|
3357
|
+
} catch {
|
|
3358
|
+
count = 0;
|
|
3359
|
+
}
|
|
3360
|
+
if (count === 1) {
|
|
3361
|
+
return {
|
|
3362
|
+
selector,
|
|
3363
|
+
count,
|
|
3364
|
+
mode: "unique"
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
if (count > 1 && !fallback) {
|
|
3368
|
+
fallback = {
|
|
3369
|
+
selector,
|
|
3370
|
+
count,
|
|
3371
|
+
mode: "fallback"
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
return fallback;
|
|
3376
|
+
}
|
|
3377
|
+
function selectInRoot(root, selectors) {
|
|
3378
|
+
if (!(root instanceof ShadowRoot)) return null;
|
|
3379
|
+
let fallback = null;
|
|
3380
|
+
for (const selector of selectors) {
|
|
3381
|
+
if (!selector) continue;
|
|
3382
|
+
let count = 0;
|
|
3383
|
+
try {
|
|
3384
|
+
count = root.querySelectorAll(selector).length;
|
|
3385
|
+
} catch {
|
|
3386
|
+
count = 0;
|
|
3387
|
+
}
|
|
3388
|
+
if (count === 1) {
|
|
3389
|
+
return {
|
|
3390
|
+
selector,
|
|
3391
|
+
count,
|
|
3392
|
+
mode: "unique"
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
if (count > 1 && !fallback) {
|
|
3396
|
+
fallback = {
|
|
3397
|
+
selector,
|
|
3398
|
+
count,
|
|
3399
|
+
mode: "fallback"
|
|
3400
|
+
};
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
return fallback;
|
|
3404
|
+
}
|
|
3405
|
+
function countInDocument(selectors) {
|
|
3406
|
+
const out = [];
|
|
3407
|
+
for (const selector of selectors) {
|
|
3408
|
+
if (!selector) continue;
|
|
3409
|
+
let count = 0;
|
|
3410
|
+
try {
|
|
3411
|
+
count = document.querySelectorAll(selector).length;
|
|
3412
|
+
} catch {
|
|
3413
|
+
count = 0;
|
|
3414
|
+
}
|
|
3415
|
+
out.push({ selector, count });
|
|
3416
|
+
}
|
|
3417
|
+
return out;
|
|
3418
|
+
}
|
|
3419
|
+
function countInRoot(root, selectors) {
|
|
3420
|
+
if (!(root instanceof ShadowRoot)) return [];
|
|
3421
|
+
const out = [];
|
|
3422
|
+
for (const selector of selectors) {
|
|
3423
|
+
if (!selector) continue;
|
|
3424
|
+
let count = 0;
|
|
3425
|
+
try {
|
|
3426
|
+
count = root.querySelectorAll(selector).length;
|
|
3427
|
+
} catch {
|
|
3428
|
+
count = 0;
|
|
3429
|
+
}
|
|
3430
|
+
out.push({ selector, count });
|
|
3431
|
+
}
|
|
3432
|
+
return out;
|
|
3433
|
+
}
|
|
3434
|
+
function isPathDebugEnabled() {
|
|
3435
|
+
const value = process.env.OPENSTEER_DEBUG_PATH || process.env.OPENSTEER_DEBUG || process.env.DEBUG_SELECTORS;
|
|
3436
|
+
if (!value) return false;
|
|
3437
|
+
const normalized = value.trim().toLowerCase();
|
|
3438
|
+
return normalized === "1" || normalized === "true";
|
|
3439
|
+
}
|
|
3440
|
+
function debugPath(message, data) {
|
|
3441
|
+
if (!isPathDebugEnabled()) return;
|
|
3442
|
+
if (data !== void 0) {
|
|
3443
|
+
console.log(`[opensteer:path] ${message}`, data);
|
|
3444
|
+
} else {
|
|
3445
|
+
console.log(`[opensteer:path] ${message}`);
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
async function disposeHandle(handle) {
|
|
3449
|
+
if (!handle) return;
|
|
3450
|
+
try {
|
|
3451
|
+
await handle.dispose();
|
|
3452
|
+
} catch {
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
// src/extract-value-normalization.ts
|
|
3457
|
+
var URL_LIST_ATTRIBUTES = /* @__PURE__ */ new Set(["srcset", "imagesrcset", "ping"]);
|
|
3458
|
+
function normalizeExtractedValue(raw, attribute) {
|
|
3459
|
+
if (raw == null) return null;
|
|
3460
|
+
const rawText = String(raw);
|
|
3461
|
+
if (!rawText.trim()) return null;
|
|
3462
|
+
const normalizedAttribute = String(attribute || "").trim().toLowerCase();
|
|
3463
|
+
if (URL_LIST_ATTRIBUTES.has(normalizedAttribute)) {
|
|
3464
|
+
const singleValue = pickSingleListAttributeValue(
|
|
3465
|
+
normalizedAttribute,
|
|
3466
|
+
rawText
|
|
3467
|
+
).trim();
|
|
3468
|
+
return singleValue || null;
|
|
3469
|
+
}
|
|
3470
|
+
const text = rawText.replace(/\s+/g, " ").trim();
|
|
3471
|
+
return text || null;
|
|
3472
|
+
}
|
|
3473
|
+
function pickSingleListAttributeValue(attribute, raw) {
|
|
3474
|
+
if (attribute === "ping") {
|
|
3475
|
+
const firstUrl = raw.trim().split(/\s+/)[0] || "";
|
|
3476
|
+
return firstUrl.trim();
|
|
3477
|
+
}
|
|
3478
|
+
if (attribute === "srcset" || attribute === "imagesrcset") {
|
|
3479
|
+
const picked = pickBestSrcsetCandidate(raw);
|
|
3480
|
+
if (picked) return picked;
|
|
3481
|
+
return pickFirstSrcsetToken(raw) || "";
|
|
3482
|
+
}
|
|
3483
|
+
return raw.trim();
|
|
3484
|
+
}
|
|
3485
|
+
function pickBestSrcsetCandidate(raw) {
|
|
3486
|
+
const candidates = parseSrcsetCandidates(raw);
|
|
3487
|
+
if (!candidates.length) return null;
|
|
3488
|
+
const widthCandidates = candidates.filter(
|
|
3489
|
+
(candidate) => typeof candidate.width === "number" && Number.isFinite(candidate.width) && candidate.width > 0
|
|
3490
|
+
);
|
|
3491
|
+
if (widthCandidates.length) {
|
|
3492
|
+
return widthCandidates.reduce(
|
|
3493
|
+
(best, candidate) => candidate.width > best.width ? candidate : best
|
|
3494
|
+
).url;
|
|
3495
|
+
}
|
|
3496
|
+
const densityCandidates = candidates.filter(
|
|
3497
|
+
(candidate) => typeof candidate.density === "number" && Number.isFinite(candidate.density) && candidate.density > 0
|
|
3498
|
+
);
|
|
3499
|
+
if (densityCandidates.length) {
|
|
3500
|
+
return densityCandidates.reduce(
|
|
3501
|
+
(best, candidate) => candidate.density > best.density ? candidate : best
|
|
3502
|
+
).url;
|
|
3503
|
+
}
|
|
3504
|
+
return candidates[0]?.url || null;
|
|
3505
|
+
}
|
|
3506
|
+
function parseSrcsetCandidates(raw) {
|
|
3507
|
+
const text = String(raw || "").trim();
|
|
3508
|
+
if (!text) return [];
|
|
3509
|
+
const out = [];
|
|
3510
|
+
let index = 0;
|
|
3511
|
+
while (index < text.length) {
|
|
3512
|
+
index = skipSeparators(text, index);
|
|
3513
|
+
if (index >= text.length) break;
|
|
3514
|
+
const urlToken = readUrlToken(text, index);
|
|
3515
|
+
index = urlToken.nextIndex;
|
|
3516
|
+
const url = urlToken.value.trim();
|
|
3517
|
+
if (!url) continue;
|
|
3518
|
+
index = skipWhitespace(text, index);
|
|
3519
|
+
const descriptors = [];
|
|
3520
|
+
while (index < text.length && text[index] !== ",") {
|
|
3521
|
+
const descriptorToken = readDescriptorToken(text, index);
|
|
3522
|
+
if (!descriptorToken.value) {
|
|
3523
|
+
index = descriptorToken.nextIndex;
|
|
3524
|
+
continue;
|
|
3525
|
+
}
|
|
3526
|
+
descriptors.push(descriptorToken.value);
|
|
3527
|
+
index = descriptorToken.nextIndex;
|
|
3528
|
+
index = skipWhitespace(text, index);
|
|
3529
|
+
}
|
|
3530
|
+
if (index < text.length && text[index] === ",") {
|
|
3531
|
+
index += 1;
|
|
3532
|
+
}
|
|
3533
|
+
let width = null;
|
|
3534
|
+
let density = null;
|
|
3535
|
+
for (const descriptor of descriptors) {
|
|
3536
|
+
const token = descriptor.trim().toLowerCase();
|
|
3537
|
+
if (!token) continue;
|
|
3538
|
+
const widthMatch = token.match(/^(\d+)w$/);
|
|
3539
|
+
if (widthMatch) {
|
|
3540
|
+
const parsed = Number.parseInt(widthMatch[1], 10);
|
|
3541
|
+
if (Number.isFinite(parsed)) {
|
|
3542
|
+
width = parsed;
|
|
3543
|
+
}
|
|
3544
|
+
continue;
|
|
3545
|
+
}
|
|
3546
|
+
const densityMatch = token.match(/^(\d*\.?\d+)x$/);
|
|
3547
|
+
if (densityMatch) {
|
|
3548
|
+
const parsed = Number.parseFloat(densityMatch[1]);
|
|
3549
|
+
if (Number.isFinite(parsed)) {
|
|
3550
|
+
density = parsed;
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
out.push({
|
|
3555
|
+
url,
|
|
3556
|
+
width,
|
|
3557
|
+
density
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
return out;
|
|
3561
|
+
}
|
|
3562
|
+
function pickFirstSrcsetToken(raw) {
|
|
3563
|
+
const candidate = parseSrcsetCandidates(raw)[0];
|
|
3564
|
+
if (candidate?.url) {
|
|
3565
|
+
return candidate.url;
|
|
3566
|
+
}
|
|
3567
|
+
const text = String(raw || "");
|
|
3568
|
+
const start = skipSeparators(text, 0);
|
|
3569
|
+
if (start >= text.length) return null;
|
|
3570
|
+
const firstToken = readUrlToken(text, start).value.trim();
|
|
3571
|
+
return firstToken || null;
|
|
3572
|
+
}
|
|
3573
|
+
function skipWhitespace(value, index) {
|
|
3574
|
+
let cursor = index;
|
|
3575
|
+
while (cursor < value.length && /\s/.test(value[cursor])) {
|
|
3576
|
+
cursor += 1;
|
|
3577
|
+
}
|
|
3578
|
+
return cursor;
|
|
3579
|
+
}
|
|
3580
|
+
function skipSeparators(value, index) {
|
|
3581
|
+
let cursor = skipWhitespace(value, index);
|
|
3582
|
+
while (cursor < value.length && value[cursor] === ",") {
|
|
3583
|
+
cursor += 1;
|
|
3584
|
+
cursor = skipWhitespace(value, cursor);
|
|
3585
|
+
}
|
|
3586
|
+
return cursor;
|
|
3587
|
+
}
|
|
3588
|
+
function readUrlToken(value, index) {
|
|
3589
|
+
let cursor = index;
|
|
3590
|
+
let out = "";
|
|
3591
|
+
const isDataUrl = value.slice(index, index + 5).toLowerCase().startsWith("data:");
|
|
3592
|
+
while (cursor < value.length) {
|
|
3593
|
+
const char = value[cursor];
|
|
3594
|
+
if (/\s/.test(char)) {
|
|
3595
|
+
break;
|
|
3596
|
+
}
|
|
3597
|
+
if (char === "," && !isDataUrl) {
|
|
3598
|
+
break;
|
|
3599
|
+
}
|
|
3600
|
+
out += char;
|
|
3601
|
+
cursor += 1;
|
|
3602
|
+
}
|
|
3603
|
+
if (isDataUrl && out.endsWith(",") && cursor < value.length) {
|
|
3604
|
+
out = out.slice(0, -1);
|
|
3605
|
+
}
|
|
3606
|
+
return {
|
|
3607
|
+
value: out,
|
|
3608
|
+
nextIndex: cursor
|
|
3609
|
+
};
|
|
3610
|
+
}
|
|
3611
|
+
function readDescriptorToken(value, index) {
|
|
3612
|
+
let cursor = skipWhitespace(value, index);
|
|
3613
|
+
let out = "";
|
|
3614
|
+
while (cursor < value.length) {
|
|
3615
|
+
const char = value[cursor];
|
|
3616
|
+
if (char === "," || /\s/.test(char)) {
|
|
3617
|
+
break;
|
|
3618
|
+
}
|
|
3619
|
+
out += char;
|
|
3620
|
+
cursor += 1;
|
|
3621
|
+
}
|
|
3622
|
+
return {
|
|
3623
|
+
value: out.trim(),
|
|
3624
|
+
nextIndex: cursor
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3628
|
+
// src/html/counter-runtime.ts
|
|
3629
|
+
var CounterResolutionError = class extends Error {
|
|
3630
|
+
code;
|
|
3631
|
+
constructor(code, message) {
|
|
3632
|
+
super(message);
|
|
3633
|
+
this.name = "CounterResolutionError";
|
|
3634
|
+
this.code = code;
|
|
3635
|
+
}
|
|
3636
|
+
};
|
|
3637
|
+
async function resolveCounterElement(page, counter) {
|
|
3638
|
+
const normalized = normalizeCounter(counter);
|
|
3639
|
+
if (normalized == null) {
|
|
3640
|
+
throw buildCounterNotFoundError(counter);
|
|
3641
|
+
}
|
|
3642
|
+
const scan = await scanCounterOccurrences(page, [normalized]);
|
|
3643
|
+
const entry = scan.get(normalized);
|
|
3644
|
+
if (!entry || entry.count <= 0 || !entry.frame) {
|
|
3645
|
+
throw buildCounterNotFoundError(counter);
|
|
3646
|
+
}
|
|
3647
|
+
if (entry.count > 1) {
|
|
3648
|
+
throw buildCounterAmbiguousError(counter);
|
|
3649
|
+
}
|
|
3650
|
+
const handle = await resolveUniqueHandleInFrame(entry.frame, normalized);
|
|
3651
|
+
const element = handle.asElement();
|
|
3652
|
+
if (!element) {
|
|
3653
|
+
await handle.dispose();
|
|
3654
|
+
throw buildCounterNotFoundError(counter);
|
|
3655
|
+
}
|
|
3656
|
+
return element;
|
|
3657
|
+
}
|
|
3658
|
+
async function resolveCountersBatch(page, requests) {
|
|
3659
|
+
const out = {};
|
|
3660
|
+
if (!requests.length) return out;
|
|
3661
|
+
const counters = dedupeCounters(requests);
|
|
3662
|
+
const scan = await scanCounterOccurrences(page, counters);
|
|
3663
|
+
for (const counter of counters) {
|
|
3664
|
+
const entry = scan.get(counter);
|
|
3665
|
+
if (entry.count > 1) {
|
|
3666
|
+
throw buildCounterAmbiguousError(counter);
|
|
3932
3667
|
}
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3668
|
+
}
|
|
3669
|
+
const valueCache = /* @__PURE__ */ new Map();
|
|
3670
|
+
for (const request of requests) {
|
|
3671
|
+
const normalized = normalizeCounter(request.counter);
|
|
3672
|
+
if (normalized == null) {
|
|
3673
|
+
out[request.key] = null;
|
|
3674
|
+
continue;
|
|
3939
3675
|
}
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
mode: "fallback"
|
|
3945
|
-
};
|
|
3676
|
+
const entry = scan.get(normalized);
|
|
3677
|
+
if (!entry || entry.count <= 0 || !entry.frame) {
|
|
3678
|
+
out[request.key] = null;
|
|
3679
|
+
continue;
|
|
3946
3680
|
}
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
if (!(root instanceof ShadowRoot)) return null;
|
|
3952
|
-
let fallback = null;
|
|
3953
|
-
for (const selector of selectors) {
|
|
3954
|
-
if (!selector) continue;
|
|
3955
|
-
let count = 0;
|
|
3956
|
-
try {
|
|
3957
|
-
count = root.querySelectorAll(selector).length;
|
|
3958
|
-
} catch {
|
|
3959
|
-
count = 0;
|
|
3681
|
+
const cacheKey = `${normalized}:${request.attribute || ""}`;
|
|
3682
|
+
if (valueCache.has(cacheKey)) {
|
|
3683
|
+
out[request.key] = valueCache.get(cacheKey);
|
|
3684
|
+
continue;
|
|
3960
3685
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3686
|
+
const read = await readCounterValueInFrame(
|
|
3687
|
+
entry.frame,
|
|
3688
|
+
normalized,
|
|
3689
|
+
request.attribute
|
|
3690
|
+
);
|
|
3691
|
+
if (read.status === "ambiguous") {
|
|
3692
|
+
throw buildCounterAmbiguousError(normalized);
|
|
3967
3693
|
}
|
|
3968
|
-
if (
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
mode: "fallback"
|
|
3973
|
-
};
|
|
3694
|
+
if (read.status === "missing") {
|
|
3695
|
+
valueCache.set(cacheKey, null);
|
|
3696
|
+
out[request.key] = null;
|
|
3697
|
+
continue;
|
|
3974
3698
|
}
|
|
3699
|
+
const normalizedValue = normalizeExtractedValue(
|
|
3700
|
+
read.value ?? null,
|
|
3701
|
+
request.attribute
|
|
3702
|
+
);
|
|
3703
|
+
valueCache.set(cacheKey, normalizedValue);
|
|
3704
|
+
out[request.key] = normalizedValue;
|
|
3975
3705
|
}
|
|
3976
|
-
return
|
|
3706
|
+
return out;
|
|
3977
3707
|
}
|
|
3978
|
-
function
|
|
3708
|
+
function dedupeCounters(requests) {
|
|
3709
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3979
3710
|
const out = [];
|
|
3980
|
-
for (const
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
} catch {
|
|
3986
|
-
count = 0;
|
|
3987
|
-
}
|
|
3988
|
-
out.push({ selector, count });
|
|
3711
|
+
for (const request of requests) {
|
|
3712
|
+
const normalized = normalizeCounter(request.counter);
|
|
3713
|
+
if (normalized == null || seen.has(normalized)) continue;
|
|
3714
|
+
seen.add(normalized);
|
|
3715
|
+
out.push(normalized);
|
|
3989
3716
|
}
|
|
3990
3717
|
return out;
|
|
3991
3718
|
}
|
|
3992
|
-
function
|
|
3993
|
-
if (!(
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3719
|
+
function normalizeCounter(counter) {
|
|
3720
|
+
if (!Number.isFinite(counter)) return null;
|
|
3721
|
+
if (!Number.isInteger(counter)) return null;
|
|
3722
|
+
if (counter <= 0) return null;
|
|
3723
|
+
return counter;
|
|
3724
|
+
}
|
|
3725
|
+
async function scanCounterOccurrences(page, counters) {
|
|
3726
|
+
const out = /* @__PURE__ */ new Map();
|
|
3727
|
+
for (const counter of counters) {
|
|
3728
|
+
out.set(counter, {
|
|
3729
|
+
count: 0,
|
|
3730
|
+
frame: null
|
|
3731
|
+
});
|
|
3732
|
+
}
|
|
3733
|
+
if (!counters.length) return out;
|
|
3734
|
+
for (const frame of page.frames()) {
|
|
3735
|
+
let frameCounts;
|
|
3998
3736
|
try {
|
|
3999
|
-
|
|
3737
|
+
frameCounts = await frame.evaluate((candidates) => {
|
|
3738
|
+
const keys = new Set(candidates.map((value) => String(value)));
|
|
3739
|
+
const counts = {};
|
|
3740
|
+
for (const key of keys) {
|
|
3741
|
+
counts[key] = 0;
|
|
3742
|
+
}
|
|
3743
|
+
const walk = (root) => {
|
|
3744
|
+
const children = Array.from(root.children);
|
|
3745
|
+
for (const child of children) {
|
|
3746
|
+
const value = child.getAttribute("c");
|
|
3747
|
+
if (value && keys.has(value)) {
|
|
3748
|
+
counts[value] = (counts[value] || 0) + 1;
|
|
3749
|
+
}
|
|
3750
|
+
walk(child);
|
|
3751
|
+
if (child.shadowRoot) {
|
|
3752
|
+
walk(child.shadowRoot);
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
};
|
|
3756
|
+
walk(document);
|
|
3757
|
+
return counts;
|
|
3758
|
+
}, counters);
|
|
4000
3759
|
} catch {
|
|
4001
|
-
|
|
3760
|
+
continue;
|
|
3761
|
+
}
|
|
3762
|
+
for (const [rawCounter, rawCount] of Object.entries(frameCounts)) {
|
|
3763
|
+
const counter = Number.parseInt(rawCounter, 10);
|
|
3764
|
+
if (!Number.isFinite(counter)) continue;
|
|
3765
|
+
const count = Number(rawCount || 0);
|
|
3766
|
+
if (!Number.isFinite(count) || count <= 0) continue;
|
|
3767
|
+
const entry = out.get(counter);
|
|
3768
|
+
entry.count += count;
|
|
3769
|
+
if (!entry.frame) {
|
|
3770
|
+
entry.frame = frame;
|
|
3771
|
+
}
|
|
4002
3772
|
}
|
|
4003
|
-
out.push({ selector, count });
|
|
4004
3773
|
}
|
|
4005
3774
|
return out;
|
|
4006
3775
|
}
|
|
4007
|
-
function
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
3776
|
+
async function resolveUniqueHandleInFrame(frame, counter) {
|
|
3777
|
+
return frame.evaluateHandle((targetCounter) => {
|
|
3778
|
+
const matches = [];
|
|
3779
|
+
const walk = (root) => {
|
|
3780
|
+
const children = Array.from(root.children);
|
|
3781
|
+
for (const child of children) {
|
|
3782
|
+
if (child.getAttribute("c") === targetCounter) {
|
|
3783
|
+
matches.push(child);
|
|
3784
|
+
}
|
|
3785
|
+
walk(child);
|
|
3786
|
+
if (child.shadowRoot) {
|
|
3787
|
+
walk(child.shadowRoot);
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
};
|
|
3791
|
+
walk(document);
|
|
3792
|
+
if (matches.length !== 1) {
|
|
3793
|
+
return null;
|
|
3794
|
+
}
|
|
3795
|
+
return matches[0];
|
|
3796
|
+
}, String(counter));
|
|
4020
3797
|
}
|
|
4021
|
-
async function
|
|
4022
|
-
if (!handle) return;
|
|
3798
|
+
async function readCounterValueInFrame(frame, counter, attribute) {
|
|
4023
3799
|
try {
|
|
4024
|
-
await
|
|
3800
|
+
return await frame.evaluate(
|
|
3801
|
+
({ targetCounter, attribute: attribute2 }) => {
|
|
3802
|
+
const matches = [];
|
|
3803
|
+
const walk = (root) => {
|
|
3804
|
+
const children = Array.from(root.children);
|
|
3805
|
+
for (const child of children) {
|
|
3806
|
+
if (child.getAttribute("c") === targetCounter) {
|
|
3807
|
+
matches.push(child);
|
|
3808
|
+
}
|
|
3809
|
+
walk(child);
|
|
3810
|
+
if (child.shadowRoot) {
|
|
3811
|
+
walk(child.shadowRoot);
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
};
|
|
3815
|
+
walk(document);
|
|
3816
|
+
if (!matches.length) {
|
|
3817
|
+
return {
|
|
3818
|
+
status: "missing"
|
|
3819
|
+
};
|
|
3820
|
+
}
|
|
3821
|
+
if (matches.length > 1) {
|
|
3822
|
+
return {
|
|
3823
|
+
status: "ambiguous"
|
|
3824
|
+
};
|
|
3825
|
+
}
|
|
3826
|
+
const target = matches[0];
|
|
3827
|
+
const value = attribute2 ? target.getAttribute(attribute2) : target.textContent;
|
|
3828
|
+
return {
|
|
3829
|
+
status: "ok",
|
|
3830
|
+
value
|
|
3831
|
+
};
|
|
3832
|
+
},
|
|
3833
|
+
{
|
|
3834
|
+
targetCounter: String(counter),
|
|
3835
|
+
attribute
|
|
3836
|
+
}
|
|
3837
|
+
);
|
|
4025
3838
|
} catch {
|
|
3839
|
+
return {
|
|
3840
|
+
status: "missing"
|
|
3841
|
+
};
|
|
4026
3842
|
}
|
|
4027
3843
|
}
|
|
3844
|
+
function buildCounterNotFoundError(counter) {
|
|
3845
|
+
return new CounterResolutionError(
|
|
3846
|
+
"ERR_COUNTER_NOT_FOUND",
|
|
3847
|
+
`Counter ${counter} was not found in the live DOM.`
|
|
3848
|
+
);
|
|
3849
|
+
}
|
|
3850
|
+
function buildCounterAmbiguousError(counter) {
|
|
3851
|
+
return new CounterResolutionError(
|
|
3852
|
+
"ERR_COUNTER_AMBIGUOUS",
|
|
3853
|
+
`Counter ${counter} matches multiple live elements.`
|
|
3854
|
+
);
|
|
3855
|
+
}
|
|
4028
3856
|
|
|
4029
3857
|
// src/actions/actionability-probe.ts
|
|
4030
3858
|
async function probeActionabilityState(element) {
|
|
@@ -4217,13 +4045,6 @@ function classifyTypedError(error) {
|
|
|
4217
4045
|
classificationSource: "typed_error"
|
|
4218
4046
|
});
|
|
4219
4047
|
}
|
|
4220
|
-
if (error.code === "ERR_COUNTER_FRAME_UNAVAILABLE") {
|
|
4221
|
-
return buildFailure({
|
|
4222
|
-
code: "TARGET_UNAVAILABLE",
|
|
4223
|
-
message: error.message,
|
|
4224
|
-
classificationSource: "typed_error"
|
|
4225
|
-
});
|
|
4226
|
-
}
|
|
4227
4048
|
if (error.code === "ERR_COUNTER_AMBIGUOUS") {
|
|
4228
4049
|
return buildFailure({
|
|
4229
4050
|
code: "TARGET_AMBIGUOUS",
|
|
@@ -4231,13 +4052,6 @@ function classifyTypedError(error) {
|
|
|
4231
4052
|
classificationSource: "typed_error"
|
|
4232
4053
|
});
|
|
4233
4054
|
}
|
|
4234
|
-
if (error.code === "ERR_COUNTER_STALE_OR_NOT_FOUND") {
|
|
4235
|
-
return buildFailure({
|
|
4236
|
-
code: "TARGET_STALE",
|
|
4237
|
-
message: error.message,
|
|
4238
|
-
classificationSource: "typed_error"
|
|
4239
|
-
});
|
|
4240
|
-
}
|
|
4241
4055
|
}
|
|
4242
4056
|
return null;
|
|
4243
4057
|
}
|
|
@@ -7588,7 +7402,7 @@ function sleep3(ms) {
|
|
|
7588
7402
|
}
|
|
7589
7403
|
|
|
7590
7404
|
// src/opensteer.ts
|
|
7591
|
-
import { createHash, randomUUID
|
|
7405
|
+
import { createHash, randomUUID } from "crypto";
|
|
7592
7406
|
|
|
7593
7407
|
// src/browser/pool.ts
|
|
7594
7408
|
import {
|
|
@@ -10368,7 +10182,7 @@ var Opensteer = class _Opensteer {
|
|
|
10368
10182
|
let persistPath = null;
|
|
10369
10183
|
try {
|
|
10370
10184
|
if (storageKey && resolution.shouldPersist) {
|
|
10371
|
-
persistPath = await this.
|
|
10185
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10372
10186
|
handle,
|
|
10373
10187
|
"hover",
|
|
10374
10188
|
resolution.counter
|
|
@@ -10467,7 +10281,7 @@ var Opensteer = class _Opensteer {
|
|
|
10467
10281
|
let persistPath = null;
|
|
10468
10282
|
try {
|
|
10469
10283
|
if (storageKey && resolution.shouldPersist) {
|
|
10470
|
-
persistPath = await this.
|
|
10284
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10471
10285
|
handle,
|
|
10472
10286
|
"input",
|
|
10473
10287
|
resolution.counter
|
|
@@ -10570,7 +10384,7 @@ var Opensteer = class _Opensteer {
|
|
|
10570
10384
|
let persistPath = null;
|
|
10571
10385
|
try {
|
|
10572
10386
|
if (storageKey && resolution.shouldPersist) {
|
|
10573
|
-
persistPath = await this.
|
|
10387
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10574
10388
|
handle,
|
|
10575
10389
|
"select",
|
|
10576
10390
|
resolution.counter
|
|
@@ -10680,7 +10494,7 @@ var Opensteer = class _Opensteer {
|
|
|
10680
10494
|
let persistPath = null;
|
|
10681
10495
|
try {
|
|
10682
10496
|
if (storageKey && resolution.shouldPersist) {
|
|
10683
|
-
persistPath = await this.
|
|
10497
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10684
10498
|
handle,
|
|
10685
10499
|
"scroll",
|
|
10686
10500
|
resolution.counter
|
|
@@ -10976,17 +10790,19 @@ var Opensteer = class _Opensteer {
|
|
|
10976
10790
|
const handle = await this.resolveCounterHandle(resolution.counter);
|
|
10977
10791
|
try {
|
|
10978
10792
|
if (storageKey && resolution.shouldPersist) {
|
|
10979
|
-
const persistPath = await this.
|
|
10793
|
+
const persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
10980
10794
|
handle,
|
|
10981
10795
|
method,
|
|
10982
10796
|
resolution.counter
|
|
10983
10797
|
);
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10798
|
+
if (persistPath) {
|
|
10799
|
+
this.persistPath(
|
|
10800
|
+
storageKey,
|
|
10801
|
+
method,
|
|
10802
|
+
options.description,
|
|
10803
|
+
persistPath
|
|
10804
|
+
);
|
|
10805
|
+
}
|
|
10990
10806
|
}
|
|
10991
10807
|
return await counterFn(handle);
|
|
10992
10808
|
} catch (err) {
|
|
@@ -11024,7 +10840,7 @@ var Opensteer = class _Opensteer {
|
|
|
11024
10840
|
let persistPath = null;
|
|
11025
10841
|
try {
|
|
11026
10842
|
if (storageKey && resolution.shouldPersist) {
|
|
11027
|
-
persistPath = await this.
|
|
10843
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
11028
10844
|
handle,
|
|
11029
10845
|
"uploadFile",
|
|
11030
10846
|
resolution.counter
|
|
@@ -11300,7 +11116,7 @@ var Opensteer = class _Opensteer {
|
|
|
11300
11116
|
let persistPath = null;
|
|
11301
11117
|
try {
|
|
11302
11118
|
if (storageKey && resolution.shouldPersist) {
|
|
11303
|
-
persistPath = await this.
|
|
11119
|
+
persistPath = await this.tryBuildPathFromResolvedHandle(
|
|
11304
11120
|
handle,
|
|
11305
11121
|
"click",
|
|
11306
11122
|
resolution.counter
|
|
@@ -11396,17 +11212,6 @@ var Opensteer = class _Opensteer {
|
|
|
11396
11212
|
}
|
|
11397
11213
|
}
|
|
11398
11214
|
if (options.element != null) {
|
|
11399
|
-
const pathFromElement = await this.tryBuildPathFromCounter(
|
|
11400
|
-
options.element
|
|
11401
|
-
);
|
|
11402
|
-
if (pathFromElement) {
|
|
11403
|
-
return {
|
|
11404
|
-
path: pathFromElement,
|
|
11405
|
-
counter: null,
|
|
11406
|
-
shouldPersist: Boolean(storageKey),
|
|
11407
|
-
source: "element"
|
|
11408
|
-
};
|
|
11409
|
-
}
|
|
11410
11215
|
return {
|
|
11411
11216
|
path: null,
|
|
11412
11217
|
counter: options.element,
|
|
@@ -11434,17 +11239,6 @@ var Opensteer = class _Opensteer {
|
|
|
11434
11239
|
options.description
|
|
11435
11240
|
);
|
|
11436
11241
|
if (resolved?.counter != null) {
|
|
11437
|
-
const pathFromAiCounter = await this.tryBuildPathFromCounter(
|
|
11438
|
-
resolved.counter
|
|
11439
|
-
);
|
|
11440
|
-
if (pathFromAiCounter) {
|
|
11441
|
-
return {
|
|
11442
|
-
path: pathFromAiCounter,
|
|
11443
|
-
counter: null,
|
|
11444
|
-
shouldPersist: Boolean(storageKey),
|
|
11445
|
-
source: "ai"
|
|
11446
|
-
};
|
|
11447
|
-
}
|
|
11448
11242
|
return {
|
|
11449
11243
|
path: null,
|
|
11450
11244
|
counter: resolved.counter,
|
|
@@ -11526,23 +11320,22 @@ var Opensteer = class _Opensteer {
|
|
|
11526
11320
|
try {
|
|
11527
11321
|
const builtPath = await buildElementPathFromHandle(handle);
|
|
11528
11322
|
if (builtPath) {
|
|
11529
|
-
|
|
11323
|
+
const withFrameContext = await this.withHandleIframeContext(
|
|
11324
|
+
handle,
|
|
11325
|
+
builtPath
|
|
11326
|
+
);
|
|
11327
|
+
return this.withIndexedIframeContext(
|
|
11328
|
+
withFrameContext,
|
|
11329
|
+
indexedPath
|
|
11330
|
+
);
|
|
11530
11331
|
}
|
|
11531
11332
|
return indexedPath;
|
|
11532
11333
|
} finally {
|
|
11533
11334
|
await handle.dispose();
|
|
11534
11335
|
}
|
|
11535
11336
|
}
|
|
11536
|
-
async tryBuildPathFromCounter(counter) {
|
|
11537
|
-
try {
|
|
11538
|
-
return await this.buildPathFromElement(counter);
|
|
11539
|
-
} catch {
|
|
11540
|
-
return null;
|
|
11541
|
-
}
|
|
11542
|
-
}
|
|
11543
11337
|
async resolveCounterHandle(element) {
|
|
11544
|
-
|
|
11545
|
-
return resolveCounterElement(this.page, snapshot, element);
|
|
11338
|
+
return resolveCounterElement(this.page, element);
|
|
11546
11339
|
}
|
|
11547
11340
|
async resolveCounterHandleForAction(action, description, element) {
|
|
11548
11341
|
try {
|
|
@@ -11566,8 +11359,12 @@ var Opensteer = class _Opensteer {
|
|
|
11566
11359
|
const indexedPath = await this.readPathFromCounterIndex(counter);
|
|
11567
11360
|
const builtPath = await buildElementPathFromHandle(handle);
|
|
11568
11361
|
if (builtPath) {
|
|
11362
|
+
const withFrameContext = await this.withHandleIframeContext(
|
|
11363
|
+
handle,
|
|
11364
|
+
builtPath
|
|
11365
|
+
);
|
|
11569
11366
|
const normalized = this.withIndexedIframeContext(
|
|
11570
|
-
|
|
11367
|
+
withFrameContext,
|
|
11571
11368
|
indexedPath
|
|
11572
11369
|
);
|
|
11573
11370
|
if (normalized.nodes.length) return normalized;
|
|
@@ -11577,15 +11374,34 @@ var Opensteer = class _Opensteer {
|
|
|
11577
11374
|
`Unable to build element path from counter ${counter} during ${action}.`
|
|
11578
11375
|
);
|
|
11579
11376
|
}
|
|
11377
|
+
async tryBuildPathFromResolvedHandle(handle, action, counter) {
|
|
11378
|
+
try {
|
|
11379
|
+
return await this.buildPathFromResolvedHandle(handle, action, counter);
|
|
11380
|
+
} catch (error) {
|
|
11381
|
+
this.logDebugError(
|
|
11382
|
+
`path persistence skipped for ${action} counter ${counter}`,
|
|
11383
|
+
error
|
|
11384
|
+
);
|
|
11385
|
+
return null;
|
|
11386
|
+
}
|
|
11387
|
+
}
|
|
11580
11388
|
withIndexedIframeContext(builtPath, indexedPath) {
|
|
11581
11389
|
const normalizedBuilt = this.normalizePath(builtPath);
|
|
11582
11390
|
if (!indexedPath) return normalizedBuilt;
|
|
11583
11391
|
const iframePrefix = collectIframeContextPrefix(indexedPath);
|
|
11584
11392
|
if (!iframePrefix.length) return normalizedBuilt;
|
|
11393
|
+
const builtContext = cloneContextHops(normalizedBuilt.context);
|
|
11394
|
+
const overlap = measureContextOverlap(iframePrefix, builtContext);
|
|
11395
|
+
const missingPrefix = cloneContextHops(
|
|
11396
|
+
iframePrefix.slice(0, iframePrefix.length - overlap)
|
|
11397
|
+
);
|
|
11398
|
+
if (!missingPrefix.length) {
|
|
11399
|
+
return normalizedBuilt;
|
|
11400
|
+
}
|
|
11585
11401
|
const merged = {
|
|
11586
11402
|
context: [
|
|
11587
|
-
...
|
|
11588
|
-
...
|
|
11403
|
+
...missingPrefix,
|
|
11404
|
+
...builtContext
|
|
11589
11405
|
],
|
|
11590
11406
|
nodes: cloneElementPath(normalizedBuilt).nodes
|
|
11591
11407
|
};
|
|
@@ -11595,9 +11411,48 @@ var Opensteer = class _Opensteer {
|
|
|
11595
11411
|
if (fallback.nodes.length) return fallback;
|
|
11596
11412
|
return normalizedBuilt;
|
|
11597
11413
|
}
|
|
11414
|
+
async withHandleIframeContext(handle, path5) {
|
|
11415
|
+
const ownFrame = await handle.ownerFrame();
|
|
11416
|
+
if (!ownFrame) {
|
|
11417
|
+
return this.normalizePath(path5);
|
|
11418
|
+
}
|
|
11419
|
+
let frame = ownFrame;
|
|
11420
|
+
let prefix = [];
|
|
11421
|
+
while (frame && frame !== this.page.mainFrame()) {
|
|
11422
|
+
const parent = frame.parentFrame();
|
|
11423
|
+
if (!parent) break;
|
|
11424
|
+
const frameElement = await frame.frameElement().catch(() => null);
|
|
11425
|
+
if (!frameElement) break;
|
|
11426
|
+
try {
|
|
11427
|
+
const frameElementPath = await buildElementPathFromHandle(frameElement);
|
|
11428
|
+
if (frameElementPath?.nodes.length) {
|
|
11429
|
+
const segment = [
|
|
11430
|
+
...cloneContextHops(frameElementPath.context),
|
|
11431
|
+
{
|
|
11432
|
+
kind: "iframe",
|
|
11433
|
+
host: cloneElementPath(frameElementPath).nodes
|
|
11434
|
+
}
|
|
11435
|
+
];
|
|
11436
|
+
prefix = [...segment, ...prefix];
|
|
11437
|
+
}
|
|
11438
|
+
} finally {
|
|
11439
|
+
await frameElement.dispose().catch(() => void 0);
|
|
11440
|
+
}
|
|
11441
|
+
frame = parent;
|
|
11442
|
+
}
|
|
11443
|
+
if (!prefix.length) {
|
|
11444
|
+
return this.normalizePath(path5);
|
|
11445
|
+
}
|
|
11446
|
+
return this.normalizePath({
|
|
11447
|
+
context: [...prefix, ...cloneContextHops(path5.context)],
|
|
11448
|
+
nodes: cloneElementPath(path5).nodes
|
|
11449
|
+
});
|
|
11450
|
+
}
|
|
11598
11451
|
async readPathFromCounterIndex(counter) {
|
|
11599
|
-
|
|
11600
|
-
|
|
11452
|
+
if (!this.snapshotCache || this.snapshotCache.url !== this.page.url() || !this.snapshotCache.counterIndex) {
|
|
11453
|
+
return null;
|
|
11454
|
+
}
|
|
11455
|
+
const indexed = this.snapshotCache.counterIndex.get(counter);
|
|
11601
11456
|
if (!indexed) return null;
|
|
11602
11457
|
const normalized = this.normalizePath(indexed);
|
|
11603
11458
|
if (!normalized.nodes.length) return null;
|
|
@@ -11608,15 +11463,6 @@ var Opensteer = class _Opensteer {
|
|
|
11608
11463
|
if (!path5) return null;
|
|
11609
11464
|
return this.normalizePath(path5);
|
|
11610
11465
|
}
|
|
11611
|
-
async ensureSnapshotWithCounters() {
|
|
11612
|
-
if (!this.snapshotCache || !this.snapshotCache.counterBindings || this.snapshotCache.url !== this.page.url()) {
|
|
11613
|
-
await this.snapshot({
|
|
11614
|
-
mode: "full",
|
|
11615
|
-
withCounters: true
|
|
11616
|
-
});
|
|
11617
|
-
}
|
|
11618
|
-
return this.snapshotCache;
|
|
11619
|
-
}
|
|
11620
11466
|
persistPath(id, method, description, path5) {
|
|
11621
11467
|
const now = Date.now();
|
|
11622
11468
|
const safeFile = this.storage.getSelectorFileName(id);
|
|
@@ -11866,17 +11712,6 @@ var Opensteer = class _Opensteer {
|
|
|
11866
11712
|
return;
|
|
11867
11713
|
}
|
|
11868
11714
|
if (normalized.element != null) {
|
|
11869
|
-
const path5 = await this.tryBuildPathFromCounter(
|
|
11870
|
-
normalized.element
|
|
11871
|
-
);
|
|
11872
|
-
if (path5) {
|
|
11873
|
-
fields.push({
|
|
11874
|
-
key: fieldKey,
|
|
11875
|
-
path: path5,
|
|
11876
|
-
attribute: normalized.attribute
|
|
11877
|
-
});
|
|
11878
|
-
return;
|
|
11879
|
-
}
|
|
11880
11715
|
fields.push({
|
|
11881
11716
|
key: fieldKey,
|
|
11882
11717
|
counter: normalized.element,
|
|
@@ -11921,15 +11756,6 @@ var Opensteer = class _Opensteer {
|
|
|
11921
11756
|
continue;
|
|
11922
11757
|
}
|
|
11923
11758
|
if (fieldPlan.element != null) {
|
|
11924
|
-
const path6 = await this.tryBuildPathFromCounter(fieldPlan.element);
|
|
11925
|
-
if (path6) {
|
|
11926
|
-
fields.push({
|
|
11927
|
-
key,
|
|
11928
|
-
path: path6,
|
|
11929
|
-
attribute: fieldPlan.attribute
|
|
11930
|
-
});
|
|
11931
|
-
continue;
|
|
11932
|
-
}
|
|
11933
11759
|
fields.push({
|
|
11934
11760
|
key,
|
|
11935
11761
|
counter: fieldPlan.element,
|
|
@@ -11984,12 +11810,7 @@ var Opensteer = class _Opensteer {
|
|
|
11984
11810
|
}
|
|
11985
11811
|
}
|
|
11986
11812
|
if (counterRequests.length) {
|
|
11987
|
-
const
|
|
11988
|
-
const counterValues = await resolveCountersBatch(
|
|
11989
|
-
this.page,
|
|
11990
|
-
snapshot,
|
|
11991
|
-
counterRequests
|
|
11992
|
-
);
|
|
11813
|
+
const counterValues = await resolveCountersBatch(this.page, counterRequests);
|
|
11993
11814
|
Object.assign(result, counterValues);
|
|
11994
11815
|
}
|
|
11995
11816
|
if (pathFields.length) {
|
|
@@ -12019,7 +11840,7 @@ var Opensteer = class _Opensteer {
|
|
|
12019
11840
|
const path5 = await this.buildPathFromElement(field.counter);
|
|
12020
11841
|
if (!path5) {
|
|
12021
11842
|
throw new Error(
|
|
12022
|
-
`Unable to
|
|
11843
|
+
`Unable to persist extraction schema field "${field.key}": counter ${field.counter} could not be converted into a stable element path.`
|
|
12023
11844
|
);
|
|
12024
11845
|
}
|
|
12025
11846
|
resolved.push({
|
|
@@ -12065,6 +11886,33 @@ function collectIframeContextPrefix(path5) {
|
|
|
12065
11886
|
if (lastIframeIndex < 0) return [];
|
|
12066
11887
|
return cloneContextHops(context.slice(0, lastIframeIndex + 1));
|
|
12067
11888
|
}
|
|
11889
|
+
function measureContextOverlap(indexedPrefix, builtContext) {
|
|
11890
|
+
const maxOverlap = Math.min(indexedPrefix.length, builtContext.length);
|
|
11891
|
+
for (let size = maxOverlap; size > 0; size -= 1) {
|
|
11892
|
+
if (matchesContextPrefix(indexedPrefix, builtContext, size, true)) {
|
|
11893
|
+
return size;
|
|
11894
|
+
}
|
|
11895
|
+
}
|
|
11896
|
+
for (let size = maxOverlap; size > 0; size -= 1) {
|
|
11897
|
+
if (matchesContextPrefix(indexedPrefix, builtContext, size, false)) {
|
|
11898
|
+
return size;
|
|
11899
|
+
}
|
|
11900
|
+
}
|
|
11901
|
+
return 0;
|
|
11902
|
+
}
|
|
11903
|
+
function matchesContextPrefix(indexedPrefix, builtContext, size, strictHost) {
|
|
11904
|
+
for (let idx = 0; idx < size; idx += 1) {
|
|
11905
|
+
const left = indexedPrefix[indexedPrefix.length - size + idx];
|
|
11906
|
+
const right = builtContext[idx];
|
|
11907
|
+
if (left.kind !== right.kind) {
|
|
11908
|
+
return false;
|
|
11909
|
+
}
|
|
11910
|
+
if (strictHost && JSON.stringify(left.host) !== JSON.stringify(right.host)) {
|
|
11911
|
+
return false;
|
|
11912
|
+
}
|
|
11913
|
+
}
|
|
11914
|
+
return true;
|
|
11915
|
+
}
|
|
12068
11916
|
function normalizeSchemaValue(value) {
|
|
12069
11917
|
if (!value) return null;
|
|
12070
11918
|
if (typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -12322,7 +12170,7 @@ function isInternalOrBlankPageUrl(url) {
|
|
|
12322
12170
|
}
|
|
12323
12171
|
function buildLocalRunId(namespace) {
|
|
12324
12172
|
const normalized = namespace.trim() || "default";
|
|
12325
|
-
return `${normalized}-${Date.now().toString(36)}-${
|
|
12173
|
+
return `${normalized}-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
12326
12174
|
}
|
|
12327
12175
|
|
|
12328
12176
|
export {
|
|
@@ -12352,13 +12200,12 @@ export {
|
|
|
12352
12200
|
cleanForClickable,
|
|
12353
12201
|
cleanForScrollable,
|
|
12354
12202
|
cleanForAction,
|
|
12355
|
-
CounterResolutionError,
|
|
12356
|
-
ensureLiveCounters,
|
|
12357
|
-
resolveCounterElement,
|
|
12358
|
-
resolveCountersBatch,
|
|
12359
12203
|
prepareSnapshot,
|
|
12360
12204
|
ElementPathError,
|
|
12361
12205
|
resolveElementPath,
|
|
12206
|
+
CounterResolutionError,
|
|
12207
|
+
resolveCounterElement,
|
|
12208
|
+
resolveCountersBatch,
|
|
12362
12209
|
performClick,
|
|
12363
12210
|
performHover,
|
|
12364
12211
|
performInput,
|