brave-real-browser-mcp-server 2.23.0 → 2.23.2

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.
@@ -24,6 +24,85 @@ export class SelfHealingLocators {
24
24
  'class', 'aria-role', 'title', 'placeholder', 'alt',
25
25
  'href', 'src', 'type', 'value', 'data-*'
26
26
  ];
27
+ /**
28
+ * Parse Playwright-style :has-text() selector
29
+ * Example: "button:has-text('HubCloud')" -> { tagName: 'button', text: 'HubCloud' }
30
+ * Also supports: "button:contains('text')" and ":text('value')"
31
+ */
32
+ parseTextSelector(selector) {
33
+ // Match :has-text("text") or :has-text('text')
34
+ const hasTextMatch = selector.match(/^([a-zA-Z]*)(?:\.[^\s:]+)*:has-text\(["']([^"']+)["']\)/i);
35
+ if (hasTextMatch) {
36
+ return {
37
+ tagName: hasTextMatch[1] || undefined,
38
+ text: hasTextMatch[2]
39
+ };
40
+ }
41
+ // Match :contains("text") jQuery-style
42
+ const containsMatch = selector.match(/^([a-zA-Z]*)(?:\.[^\s:]+)*:contains\(["']([^"']+)["']\)/i);
43
+ if (containsMatch) {
44
+ return {
45
+ tagName: containsMatch[1] || undefined,
46
+ text: containsMatch[2]
47
+ };
48
+ }
49
+ // Match :text("value") Playwright-style
50
+ const textMatch = selector.match(/:text\(["']([^"']+)["']\)/i);
51
+ if (textMatch) {
52
+ return {
53
+ tagName: undefined,
54
+ text: textMatch[1]
55
+ };
56
+ }
57
+ return null;
58
+ }
59
+ /**
60
+ * Find element by text content (for :has-text() support)
61
+ */
62
+ async findByText(pageInstance, text, tagName) {
63
+ try {
64
+ const element = await pageInstance.evaluateHandle((searchText, tag) => {
65
+ const selector = tag || '*';
66
+ const elements = Array.from(document.querySelectorAll(selector));
67
+ // Find element that contains or exactly matches the text
68
+ const found = elements.find(el => {
69
+ // Skip script and style elements
70
+ if (el.tagName === 'SCRIPT' || el.tagName === 'STYLE')
71
+ return false;
72
+ const textContent = el.textContent?.trim() || '';
73
+ const directText = Array.from(el.childNodes)
74
+ .filter(n => n.nodeType === Node.TEXT_NODE)
75
+ .map(n => n.textContent?.trim())
76
+ .join('');
77
+ // Check direct text first (more accurate)
78
+ if (directText.includes(searchText) || directText === searchText)
79
+ return true;
80
+ // Check if element's own text contains the search text
81
+ // but avoid matching parent elements that contain the text in children
82
+ if (el.children.length === 0 && textContent.includes(searchText))
83
+ return true;
84
+ // For buttons/links with nested elements (icons, spans), check full text
85
+ if (['BUTTON', 'A', 'DIV'].includes(el.tagName) && textContent.includes(searchText)) {
86
+ return true;
87
+ }
88
+ return false;
89
+ });
90
+ return found || null;
91
+ }, text, tagName);
92
+ const asElement = element.asElement();
93
+ if (asElement) {
94
+ // Verify element is visible
95
+ const box = await asElement.boundingBox();
96
+ if (box && box.width > 0 && box.height > 0) {
97
+ return asElement;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+ catch (e) {
103
+ return null;
104
+ }
105
+ }
27
106
  /**
28
107
  * Generate fallback selectors for a failed primary selector
29
108
  */
@@ -59,6 +138,18 @@ export class SelfHealingLocators {
59
138
  * Try to find an element using fallback selectors
60
139
  */
61
140
  async findElementWithFallbacks(pageInstance, primarySelector, expectedText) {
141
+ // Check if selector uses :has-text(), :contains(), or :text() syntax
142
+ const textSelector = this.parseTextSelector(primarySelector);
143
+ if (textSelector) {
144
+ const element = await this.findByText(pageInstance, textSelector.text, textSelector.tagName);
145
+ if (element) {
146
+ return {
147
+ element,
148
+ usedSelector: `${textSelector.tagName || '*'}[text*="${textSelector.text}"]`,
149
+ strategy: 'text-content'
150
+ };
151
+ }
152
+ }
62
153
  // First try the primary selector
63
154
  try {
64
155
  const primaryElement = await pageInstance.$(primarySelector);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser-mcp-server",
3
- "version": "2.23.0",
3
+ "version": "2.23.2",
4
4
  "description": "🦁 MCP server for Brave Real Browser - NPM Workspaces Monorepo with anti-detection features, SSE streaming, and LSP compatibility",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -49,7 +49,7 @@
49
49
  "dependencies": {
50
50
  "@modelcontextprotocol/sdk": "latest",
51
51
  "@types/turndown": "latest",
52
- "brave-real-browser": "^2.4.0",
52
+ "brave-real-browser": "^2.4.2",
53
53
  "turndown": "latest",
54
54
  "vscode-languageserver": "^9.0.1",
55
55
  "vscode-languageserver-textdocument": "^1.0.12"