@trusty-squire/mcp 0.8.2-rc.21 → 0.8.2-rc.4
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/bot/agent.d.ts +0 -8
- package/dist/bot/agent.d.ts.map +1 -1
- package/dist/bot/agent.js +154 -780
- package/dist/bot/agent.js.map +1 -1
- package/dist/bot/browser.d.ts +0 -2
- package/dist/bot/browser.d.ts.map +1 -1
- package/dist/bot/browser.js +12 -247
- package/dist/bot/browser.js.map +1 -1
- package/dist/bot/llm-client.d.ts.map +1 -1
- package/dist/bot/llm-client.js +23 -119
- package/dist/bot/llm-client.js.map +1 -1
- package/dist/bot/promote-to-skill.d.ts.map +1 -1
- package/dist/bot/promote-to-skill.js +2 -50
- package/dist/bot/promote-to-skill.js.map +1 -1
- package/dist/bot/replay-skill.d.ts +0 -13
- package/dist/bot/replay-skill.d.ts.map +1 -1
- package/dist/bot/replay-skill.js +7 -71
- package/dist/bot/replay-skill.js.map +1 -1
- package/dist/install/interactive.d.ts.map +1 -1
- package/dist/install/interactive.js +9 -23
- package/dist/install/interactive.js.map +1 -1
- package/package.json +1 -1
package/dist/bot/browser.d.ts
CHANGED
|
@@ -63,8 +63,6 @@ export declare class BrowserController {
|
|
|
63
63
|
}>;
|
|
64
64
|
selectOption(selector: string, optionMatcher?: string): Promise<void>;
|
|
65
65
|
private selectFromCombobox;
|
|
66
|
-
private resolveLabelToInput;
|
|
67
|
-
private tryReactSelectKeyboardPick;
|
|
68
66
|
private pickComboboxOption;
|
|
69
67
|
private humanClick;
|
|
70
68
|
private humanClickLocator;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/bot/browser.ts"],"names":[],"mappings":"AA2BA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAgC5D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,YAAY,GAAG,SAAS,GAAG,MAAM,CAAC;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IAKvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAInB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AASpD,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,cAAc,GACd,cAAc,GACd,UAAU,GACV,SAAS,CAAC;AAed,MAAM,MAAM,kBAAkB,GAC1B;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,GAChB;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,GAChD;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AA0FtD,qBAAa,iBAAiB;IAI5B,OAAO,CAAC,OAAO,CAA+B;IAC9C,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IAGnC,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,MAAM,CAAO;IAMrB,OAAO,CAAC,eAAe,CAAuB;IAK9C,OAAO,CAAC,WAAW,CAAuB;IAE1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAMpC,OAAO,CAAC,gBAAgB,CAAqB;IAO7C,OAAO,CAAC,IAAI,CAAwB;IAKpC,OAAO,CAAC,YAAY,CACR;IAEZ,IAAI,UAAU,IAAI,SAAS,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAE5D;gBAEW,IAAI,GAAE,wBAA6B;IAS/C,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAK3B;IAKD,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAK3B;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAmNd,cAAc;YA4Cd,YAAY;IAiCpB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiChC,OAAO,CACX,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,MAAM,GAAG,gBAAyB,GACvC,OAAO,CAAC,IAAI,CAAC;YA6BF,uBAAuB;IAgE/B,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2CnD,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBtC,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IA6BjE,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8C5C,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwDtC,gBAAgB,CACpB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC;QACT,QAAQ,EAAE,OAAO,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,MAAM,EAAE,IAAI,GAAG,cAAc,GAAG,mBAAmB,CAAC;KACrD,CAAC;IA2KI,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/bot/browser.ts"],"names":[],"mappings":"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"}
|
package/dist/bot/browser.js
CHANGED
|
@@ -962,34 +962,10 @@ export class BrowserController {
|
|
|
962
962
|
if (!this.page)
|
|
963
963
|
throw new Error("Browser not started");
|
|
964
964
|
await this.page.waitForSelector(selector, { state: "attached", timeout: 10000 });
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
.locator(activeSelector)
|
|
965
|
+
const tagName = await this.page
|
|
966
|
+
.locator(selector)
|
|
968
967
|
.first()
|
|
969
968
|
.evaluate((node) => node.tagName.toLowerCase());
|
|
970
|
-
// 0.8.2-rc.21 — Railway-class fix. The captured selector frequently
|
|
971
|
-
// points at a `<label>` (the inventory ranker prefers visible-text
|
|
972
|
-
// elements). If that label's `for=` association resolves to a
|
|
973
|
-
// native `<select>`, take the native path instead of routing into
|
|
974
|
-
// selectFromCombobox — native selects don't reveal their options
|
|
975
|
-
// via any DOM pattern in headless Chromium (they're OS-rendered),
|
|
976
|
-
// so the combobox path is guaranteed to fail for them. Without
|
|
977
|
-
// this redirect, every captured Railway/legacy-form `<select>`
|
|
978
|
-
// step replays as "no options found after click."
|
|
979
|
-
if (tagName === "label") {
|
|
980
|
-
const resolved = await this.resolveLabelToInput(activeSelector);
|
|
981
|
-
if (resolved !== activeSelector) {
|
|
982
|
-
const resolvedTag = await this.page
|
|
983
|
-
.locator(resolved)
|
|
984
|
-
.first()
|
|
985
|
-
.evaluate((node) => node.tagName.toLowerCase())
|
|
986
|
-
.catch(() => "");
|
|
987
|
-
if (resolvedTag === "select") {
|
|
988
|
-
activeSelector = resolved;
|
|
989
|
-
tagName = "select";
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
969
|
if (tagName === "select") {
|
|
994
970
|
// Native path. rc.15 — keep value="" options selectable. The
|
|
995
971
|
// Railway workspace dropdown's "No workspace" option is value=""
|
|
@@ -1000,10 +976,10 @@ export class BrowserController {
|
|
|
1000
976
|
// (with the first option's value, even if empty), and a matched
|
|
1001
977
|
// text-based pick is honored verbatim — including empty values.
|
|
1002
978
|
const allValues = await this.page
|
|
1003
|
-
.locator(`${
|
|
979
|
+
.locator(`${selector} option`)
|
|
1004
980
|
.evaluateAll((opts) => opts.map((o) => (o instanceof HTMLOptionElement ? o.value : "")));
|
|
1005
981
|
if (allValues.length === 0) {
|
|
1006
|
-
throw new Error(`<select> ${
|
|
982
|
+
throw new Error(`<select> ${selector} has no selectable option`);
|
|
1007
983
|
}
|
|
1008
984
|
// Default to the first NON-empty value when the planner gave no
|
|
1009
985
|
// hint — historic behavior, kept because "Select…" placeholder
|
|
@@ -1016,7 +992,7 @@ export class BrowserController {
|
|
|
1016
992
|
// option's text matches. Wrap in an object so we can
|
|
1017
993
|
// distinguish "matched to empty value" from "no match".
|
|
1018
994
|
const matched = await this.page
|
|
1019
|
-
.locator(`${
|
|
995
|
+
.locator(`${selector} option`)
|
|
1020
996
|
.evaluateAll((opts, needle) => {
|
|
1021
997
|
const hit = opts
|
|
1022
998
|
.filter((o) => o instanceof HTMLOptionElement)
|
|
@@ -1028,9 +1004,9 @@ export class BrowserController {
|
|
|
1028
1004
|
}
|
|
1029
1005
|
}
|
|
1030
1006
|
if (chosenValue === undefined) {
|
|
1031
|
-
throw new Error(`<select> ${
|
|
1007
|
+
throw new Error(`<select> ${selector} has no selectable option`);
|
|
1032
1008
|
}
|
|
1033
|
-
await this.page.selectOption(
|
|
1009
|
+
await this.page.selectOption(selector, chosenValue);
|
|
1034
1010
|
// rc.17 — mark the element as touched so subsequent inventory
|
|
1035
1011
|
// reads can suppress the DEFAULTED-dropdown warning for it.
|
|
1036
1012
|
// Without this, a select whose committed value is "" (Railway's
|
|
@@ -1038,7 +1014,7 @@ export class BrowserController {
|
|
|
1038
1014
|
// the planner gets stuck in a select→select→… loop trying to
|
|
1039
1015
|
// satisfy a warning the form has already satisfied.
|
|
1040
1016
|
await this.page
|
|
1041
|
-
.locator(
|
|
1017
|
+
.locator(selector)
|
|
1042
1018
|
.first()
|
|
1043
1019
|
.evaluate((el) => {
|
|
1044
1020
|
if (el instanceof HTMLElement)
|
|
@@ -1049,7 +1025,7 @@ export class BrowserController {
|
|
|
1049
1025
|
}
|
|
1050
1026
|
// Custom combobox path. Sentry, Radix, Headless UI, React Aria
|
|
1051
1027
|
// — every modern React picker emits role=option on its items.
|
|
1052
|
-
await this.selectFromCombobox(
|
|
1028
|
+
await this.selectFromCombobox(selector, optionMatcher);
|
|
1053
1029
|
}
|
|
1054
1030
|
// F11 (+rc.7 hardening): click a combobox trigger, wait for the
|
|
1055
1031
|
// listbox to open, click an option.
|
|
@@ -1088,18 +1064,7 @@ export class BrowserController {
|
|
|
1088
1064
|
async selectFromCombobox(triggerSelector, optionMatcher) {
|
|
1089
1065
|
if (!this.page)
|
|
1090
1066
|
throw new Error("Browser not started");
|
|
1091
|
-
|
|
1092
|
-
// emits a selector pointing at a `<label for="X">` instead of the
|
|
1093
|
-
// associated `<input id="X">` — the label has the visible text
|
|
1094
|
-
// ("Project") so the inventory ranking surfaces it as the target.
|
|
1095
|
-
// Clicking a label is NOT equivalent to clicking the input for
|
|
1096
|
-
// react-select: the synthetic focus DOES move to the input via
|
|
1097
|
-
// the `for` association, but no mouse-down lands on the
|
|
1098
|
-
// react-select control, so the menu never opens. Resolve the
|
|
1099
|
-
// label to its associated input here so downstream tiers (the
|
|
1100
|
-
// keyboard fallback in particular) actually see an input target.
|
|
1101
|
-
const normalizedSelector = await this.resolveLabelToInput(triggerSelector);
|
|
1102
|
-
await this.humanClick(normalizedSelector);
|
|
1067
|
+
await this.humanClick(triggerSelector);
|
|
1103
1068
|
const patternSelectors = [
|
|
1104
1069
|
'[role="option"]:visible',
|
|
1105
1070
|
'[role="menuitem"]:visible',
|
|
@@ -1123,19 +1088,6 @@ export class BrowserController {
|
|
|
1123
1088
|
await this.pickComboboxOption(locator, optionMatcher);
|
|
1124
1089
|
return;
|
|
1125
1090
|
}
|
|
1126
|
-
// 0.8.2-rc.11 — keyboard-driven react-select fallback. Sentry's
|
|
1127
|
-
// permission-grid combobox (Project--permission, Team--permission,
|
|
1128
|
-
// …) is a react-select 5 instance: clicking the inner <input> only
|
|
1129
|
-
// focuses it; the menu opens on keyboard activity. The standard
|
|
1130
|
-
// pattern is: Alt+Down (or just type a character) to open + filter,
|
|
1131
|
-
// then Enter to commit. Try Alt+Down first so an instance with
|
|
1132
|
-
// visible options but no role="option" still works; then if a
|
|
1133
|
-
// matcher was given, type-to-filter + Enter so a hidden listbox
|
|
1134
|
-
// narrows directly to the right option.
|
|
1135
|
-
if (await this.tryReactSelectKeyboardPick(normalizedSelector, optionMatcher)) {
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
triedDescriptors.push("react-select keyboard (Alt+Down, type-to-filter, Enter)");
|
|
1139
1091
|
// ARIA tiers all empty. Text-based fallback, only if the planner
|
|
1140
1092
|
// told us WHICH option to pick — without a matcher, "first text
|
|
1141
1093
|
// on the page" would click unrelated UI.
|
|
@@ -1152,169 +1104,11 @@ export class BrowserController {
|
|
|
1152
1104
|
// not found — fall through to error
|
|
1153
1105
|
}
|
|
1154
1106
|
}
|
|
1155
|
-
throw new Error(`combobox ${triggerSelector}` +
|
|
1156
|
-
(normalizedSelector !== triggerSelector ? ` (normalized to ${normalizedSelector})` : "") +
|
|
1157
|
-
`: no options found after click. ` +
|
|
1107
|
+
throw new Error(`combobox ${triggerSelector}: no options found after click. ` +
|
|
1158
1108
|
`Tried: ${triedDescriptors.join(", ")}. ` +
|
|
1159
1109
|
`The trigger may not have opened a popover, or the popover uses ` +
|
|
1160
1110
|
`an option pattern this executor doesn't recognize.`);
|
|
1161
1111
|
}
|
|
1162
|
-
// 0.8.2-rc.11 — resolve a `<label for="X">` selector to `#X` so the
|
|
1163
|
-
// executor lands on the actual input rather than the label decoration.
|
|
1164
|
-
// The planner-emitted inventory line for Sentry's permission grid
|
|
1165
|
-
// sometimes targets the label (the visible text is "Project", which
|
|
1166
|
-
// lives on the <label>, not the <input>); a click on a label only
|
|
1167
|
-
// synthetically focuses its `for` target, which is insufficient to
|
|
1168
|
-
// open a react-select menu. Returns the original selector unchanged
|
|
1169
|
-
// when the resolution doesn't apply (target isn't a label, has no
|
|
1170
|
-
// `for`, or the `for`-id doesn't resolve to an input).
|
|
1171
|
-
async resolveLabelToInput(selector) {
|
|
1172
|
-
if (!this.page)
|
|
1173
|
-
throw new Error("Browser not started");
|
|
1174
|
-
try {
|
|
1175
|
-
const resolvedId = await this.page
|
|
1176
|
-
.locator(selector)
|
|
1177
|
-
.first()
|
|
1178
|
-
.evaluate((node) => {
|
|
1179
|
-
if (!(node instanceof HTMLLabelElement))
|
|
1180
|
-
return null;
|
|
1181
|
-
const forAttr = node.htmlFor;
|
|
1182
|
-
if (forAttr.length === 0)
|
|
1183
|
-
return null;
|
|
1184
|
-
const target = node.ownerDocument.getElementById(forAttr);
|
|
1185
|
-
if (target === null)
|
|
1186
|
-
return null;
|
|
1187
|
-
// Only redirect when the target is input/textarea/select. A
|
|
1188
|
-
// label pointing at a non-form element (rare; React Aria
|
|
1189
|
-
// does it for a labelled-by relationship) shouldn't trigger
|
|
1190
|
-
// the redirect.
|
|
1191
|
-
const tag = target.tagName.toLowerCase();
|
|
1192
|
-
if (tag !== "input" && tag !== "textarea" && tag !== "select") {
|
|
1193
|
-
return null;
|
|
1194
|
-
}
|
|
1195
|
-
return forAttr;
|
|
1196
|
-
});
|
|
1197
|
-
if (resolvedId === null)
|
|
1198
|
-
return selector;
|
|
1199
|
-
// CSS-escape the id so unusual characters (Sentry's `--` separator
|
|
1200
|
-
// is fine, but the helper is defensive against future ids that
|
|
1201
|
-
// include `.`, spaces, …) don't break the locator.
|
|
1202
|
-
const escaped = (typeof globalThis.CSS?.escape ===
|
|
1203
|
-
"function"
|
|
1204
|
-
? globalThis.CSS.escape(resolvedId)
|
|
1205
|
-
: resolvedId.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1"));
|
|
1206
|
-
return `#${escaped}`;
|
|
1207
|
-
}
|
|
1208
|
-
catch {
|
|
1209
|
-
return selector;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
// 0.8.2-rc.11 — keyboard-driven react-select interaction. The
|
|
1213
|
-
// trigger is the inner <input>; opening the menu via mouse click
|
|
1214
|
-
// alone isn't reliable on every react-select instance (Sentry's
|
|
1215
|
-
// permission grid). Sequence:
|
|
1216
|
-
// 1. focus the trigger (the click already happened in
|
|
1217
|
-
// selectFromCombobox, but a defensive .focus() handles the
|
|
1218
|
-
// case where the click went to a sibling overlay).
|
|
1219
|
-
// 2. press Alt+ArrowDown — react-select binds this to open the
|
|
1220
|
-
// menu and select the first option.
|
|
1221
|
-
// 3. if a matcher was given, type its first 1-3 letters to filter
|
|
1222
|
-
// the menu down to the right option, then press Enter to
|
|
1223
|
-
// commit.
|
|
1224
|
-
// 4. if no matcher, ArrowDown was already issued — press Enter to
|
|
1225
|
-
// commit the first option.
|
|
1226
|
-
// Verify via the input's aria-activedescendant or value attribute
|
|
1227
|
-
// changing (react-select updates one or the other on selection).
|
|
1228
|
-
// Returns true on success, false when the page didn't react.
|
|
1229
|
-
async tryReactSelectKeyboardPick(triggerSelector, optionMatcher) {
|
|
1230
|
-
if (!this.page)
|
|
1231
|
-
throw new Error("Browser not started");
|
|
1232
|
-
const triggerLocator = this.page.locator(triggerSelector);
|
|
1233
|
-
try {
|
|
1234
|
-
const tagName = await triggerLocator
|
|
1235
|
-
.first()
|
|
1236
|
-
.evaluate((node) => node.tagName.toLowerCase());
|
|
1237
|
-
// Limit this path to input-typed triggers; native <select> and
|
|
1238
|
-
// <button role="combobox"> are handled by other tiers. The
|
|
1239
|
-
// selectFromCombobox caller has already returned for matching
|
|
1240
|
-
// [role="option"] tiers, so we only reach here on patterns where
|
|
1241
|
-
// the trigger is an input.
|
|
1242
|
-
if (tagName !== "input")
|
|
1243
|
-
return false;
|
|
1244
|
-
}
|
|
1245
|
-
catch {
|
|
1246
|
-
return false;
|
|
1247
|
-
}
|
|
1248
|
-
try {
|
|
1249
|
-
await triggerLocator.first().focus({ timeout: 1500 });
|
|
1250
|
-
}
|
|
1251
|
-
catch {
|
|
1252
|
-
return false;
|
|
1253
|
-
}
|
|
1254
|
-
// Snapshot the input's relevant attributes BEFORE opening so we
|
|
1255
|
-
// can verify that the pick actually committed.
|
|
1256
|
-
const before = await triggerLocator
|
|
1257
|
-
.first()
|
|
1258
|
-
.evaluate((node) => ({
|
|
1259
|
-
activedescendant: node.getAttribute("aria-activedescendant") ?? "",
|
|
1260
|
-
value: node instanceof HTMLInputElement ? node.value : "",
|
|
1261
|
-
// react-select 5 mirrors the selected value into the closest
|
|
1262
|
-
// .css-{hash}-singleValue node; grab the trigger's surrounding
|
|
1263
|
-
// text so a successful pick produces an observable change.
|
|
1264
|
-
surroundingText: node.parentElement?.parentElement?.parentElement?.textContent ?? "",
|
|
1265
|
-
}))
|
|
1266
|
-
.catch(() => ({ activedescendant: "", value: "", surroundingText: "" }));
|
|
1267
|
-
// Press Alt+ArrowDown to open + highlight the first option, then
|
|
1268
|
-
// if a matcher exists, type to filter, then Enter.
|
|
1269
|
-
try {
|
|
1270
|
-
await this.page.keyboard.press("Alt+ArrowDown");
|
|
1271
|
-
}
|
|
1272
|
-
catch {
|
|
1273
|
-
return false;
|
|
1274
|
-
}
|
|
1275
|
-
// Wait briefly for the menu to render.
|
|
1276
|
-
await this.wait(0.4);
|
|
1277
|
-
if (optionMatcher !== undefined && optionMatcher.length > 0) {
|
|
1278
|
-
// Type a few characters to filter; react-select narrows on each
|
|
1279
|
-
// keystroke. Capping at 6 keeps the input from overshooting on
|
|
1280
|
-
// a long matcher when the first few characters already narrow
|
|
1281
|
-
// to a single option ("Admin" → typing "Adm" is enough).
|
|
1282
|
-
const typed = optionMatcher.slice(0, 6);
|
|
1283
|
-
try {
|
|
1284
|
-
await triggerLocator.first().pressSequentially(typed, { delay: 25 });
|
|
1285
|
-
}
|
|
1286
|
-
catch {
|
|
1287
|
-
return false;
|
|
1288
|
-
}
|
|
1289
|
-
await this.wait(0.35);
|
|
1290
|
-
}
|
|
1291
|
-
try {
|
|
1292
|
-
await this.page.keyboard.press("Enter");
|
|
1293
|
-
}
|
|
1294
|
-
catch {
|
|
1295
|
-
return false;
|
|
1296
|
-
}
|
|
1297
|
-
await this.wait(0.5);
|
|
1298
|
-
const after = await triggerLocator
|
|
1299
|
-
.first()
|
|
1300
|
-
.evaluate((node) => ({
|
|
1301
|
-
activedescendant: node.getAttribute("aria-activedescendant") ?? "",
|
|
1302
|
-
value: node instanceof HTMLInputElement ? node.value : "",
|
|
1303
|
-
surroundingText: node.parentElement?.parentElement?.parentElement?.textContent ?? "",
|
|
1304
|
-
}))
|
|
1305
|
-
.catch(() => ({ activedescendant: "", value: "", surroundingText: "" }));
|
|
1306
|
-
// A successful pick produces at least one observable change.
|
|
1307
|
-
// react-select clears the input's value once a selection commits
|
|
1308
|
-
// (the chosen label moves into a sibling singleValue node), so the
|
|
1309
|
-
// surrounding-text diff is the strongest signal.
|
|
1310
|
-
if (before.surroundingText !== after.surroundingText)
|
|
1311
|
-
return true;
|
|
1312
|
-
if (before.activedescendant !== after.activedescendant)
|
|
1313
|
-
return true;
|
|
1314
|
-
if (before.value !== after.value)
|
|
1315
|
-
return true;
|
|
1316
|
-
return false;
|
|
1317
|
-
}
|
|
1318
1112
|
// F11: pick an option from a Playwright Locator already-narrowed to
|
|
1319
1113
|
// candidates. Matcher → filter by hasText (case-insensitive by
|
|
1320
1114
|
// default in Playwright). No matcher → first.
|
|
@@ -2216,37 +2010,8 @@ export class BrowserController {
|
|
|
2216
2010
|
if (trimmed.length === 0)
|
|
2217
2011
|
return;
|
|
2218
2012
|
const masked = isMaskedShape(trimmed);
|
|
2219
|
-
if (!masked && !isCredentialShape(trimmed))
|
|
2220
|
-
// 0.8.2-rc.17 — when the whole text-node string has
|
|
2221
|
-
// whitespace (Cloudinary's "Cloud name: dlq4xgrca" sits
|
|
2222
|
-
// in a SINGLE <div> with the label and value glued
|
|
2223
|
-
// together), isCredentialShape rejects the whole string.
|
|
2224
|
-
// Try to split on the canonical label-value separator
|
|
2225
|
-
// patterns ("Label: value", "Label = value", "Label\nvalue")
|
|
2226
|
-
// and re-evaluate each side. The token side gets the
|
|
2227
|
-
// candidate slot; the label side already lives on its own
|
|
2228
|
-
// (we don't need to push it). First-wins on duplicates.
|
|
2229
|
-
const split = /^([A-Za-z][A-Za-z _-]{1,40}?)\s*[:=]\s*([A-Za-z0-9._\-]{4,256})$/.exec(trimmed);
|
|
2230
|
-
if (split === null)
|
|
2231
|
-
return;
|
|
2232
|
-
const valueToken = split[2];
|
|
2233
|
-
if (valueToken === undefined)
|
|
2234
|
-
return;
|
|
2235
|
-
if (!isCredentialShape(valueToken))
|
|
2236
|
-
return;
|
|
2237
|
-
if (seen.has(valueToken))
|
|
2238
|
-
return;
|
|
2239
|
-
seen.add(valueToken);
|
|
2240
|
-
const c = centerOf(el);
|
|
2241
|
-
const label = findNearestLabel(c.x, c.y);
|
|
2242
|
-
out.push({
|
|
2243
|
-
value: valueToken,
|
|
2244
|
-
label,
|
|
2245
|
-
isMasked: false,
|
|
2246
|
-
hasRevealButton: false,
|
|
2247
|
-
});
|
|
2013
|
+
if (!masked && !isCredentialShape(trimmed))
|
|
2248
2014
|
return;
|
|
2249
|
-
}
|
|
2250
2015
|
if (seen.has(trimmed))
|
|
2251
2016
|
return;
|
|
2252
2017
|
seen.add(trimmed);
|