haltija 1.1.21 → 1.2.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.
@@ -3,27 +3,37 @@
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
7
9
  var __toCommonJS = (from) => {
8
- var entry = __moduleCache.get(from), desc;
10
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
9
11
  if (entry)
10
12
  return entry;
11
13
  entry = __defProp({}, "__esModule", { value: true });
12
- if (from && typeof from === "object" || typeof from === "function")
13
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
- get: () => from[key],
15
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
- }));
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (var key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(entry, key))
17
+ __defProp(entry, key, {
18
+ get: __accessProp.bind(from, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
17
22
  __moduleCache.set(from, entry);
18
23
  return entry;
19
24
  };
25
+ var __moduleCache;
26
+ var __returnValue = (v) => v;
27
+ function __exportSetter(name, newValue) {
28
+ this[name] = __returnValue.bind(null, newValue);
29
+ }
20
30
  var __export = (target, all) => {
21
31
  for (var name in all)
22
32
  __defProp(target, name, {
23
33
  get: all[name],
24
34
  enumerable: true,
25
35
  configurable: true,
26
- set: (newValue) => all[name] = () => newValue
36
+ set: __exportSetter.bind(all, name)
27
37
  });
28
38
  };
29
39
 
@@ -36,7 +46,7 @@
36
46
  });
37
47
 
38
48
  // src/version.ts
39
- var VERSION = "1.1.21";
49
+ var VERSION = "1.2.2";
40
50
 
41
51
  // src/text-selector.ts
42
52
  var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
@@ -545,6 +555,40 @@
545
555
  const candidates = document.querySelectorAll(parsed.baseSelector);
546
556
  return Array.from(candidates).filter((el) => elementTextMatches(el, parsed));
547
557
  }
558
+ function resolveRefOrSelector(ref, selector) {
559
+ if (ref) {
560
+ const result2 = refRegistry.resolveWithStatus(ref);
561
+ if (result2.element) {
562
+ stats.refsResolved++;
563
+ return { element: result2.element, targetDesc: `@${ref}` };
564
+ }
565
+ stats.refsStale++;
566
+ const errorMsg = result2.status === "never_assigned" ? `Ref @${ref} was never assigned (highest ref is @${refRegistry.getStats().highWaterMark})` : result2.status === "removed_from_dom" ? `Ref @${ref} points to an element that was removed from the DOM (try refreshing /tree)` : `Ref @${ref} is stale - element was garbage collected (try refreshing /tree)`;
567
+ return { element: null, targetDesc: `@${ref}`, error: errorMsg };
568
+ }
569
+ if (selector) {
570
+ const el = resolveSelector(selector);
571
+ return { element: el, targetDesc: selector };
572
+ }
573
+ return { element: null, targetDesc: "(none)", error: "ref or selector is required" };
574
+ }
575
+ function resolveRefOrSelectorAll(ref, selector) {
576
+ if (ref) {
577
+ const result2 = refRegistry.resolveWithStatus(ref);
578
+ if (result2.element) {
579
+ stats.refsResolved++;
580
+ return { elements: [result2.element], targetDesc: `@${ref}` };
581
+ }
582
+ stats.refsStale++;
583
+ const errorMsg = result2.status === "never_assigned" ? `Ref @${ref} was never assigned (highest ref is @${refRegistry.getStats().highWaterMark})` : result2.status === "removed_from_dom" ? `Ref @${ref} points to an element that was removed from the DOM (try refreshing /tree)` : `Ref @${ref} is stale - element was garbage collected (try refreshing /tree)`;
584
+ return { elements: [], targetDesc: `@${ref}`, error: errorMsg };
585
+ }
586
+ if (selector) {
587
+ const els = resolveSelectorAll(selector);
588
+ return { elements: els, targetDesc: selector };
589
+ }
590
+ return { elements: [], targetDesc: "(none)", error: "ref or selector is required" };
591
+ }
548
592
  function extractElement(el) {
549
593
  const rect = el.getBoundingClientRect();
550
594
  const attrs = {};
@@ -1650,6 +1694,10 @@
1650
1694
  recording = null;
1651
1695
  testRecording = null;
1652
1696
  originalConsole = {};
1697
+ originalDialogs = {};
1698
+ dialogPolicy = { alert: "dismiss", confirm: "accept", prompt: "dismiss", beforeunload: "allow" };
1699
+ dialogHistory = [];
1700
+ _inHaltijaUI = false;
1653
1701
  widgetHidden = false;
1654
1702
  serverUrl = "wss://localhost:8700/ws/browser";
1655
1703
  windowId;
@@ -1835,12 +1883,14 @@
1835
1883
  this.style.bottom = `${this.homeBottom}px`;
1836
1884
  this.setupKeyboardShortcut();
1837
1885
  this.interceptConsole();
1886
+ this.interceptDialogs();
1838
1887
  this.connect();
1839
1888
  }
1840
1889
  disconnectedCallback() {
1841
1890
  this.killed = true;
1842
1891
  this.disconnect();
1843
1892
  this.restoreConsole();
1893
+ this.restoreDialogs();
1844
1894
  this.clearEventWatchers();
1845
1895
  this.stopMutationWatch();
1846
1896
  }
@@ -3994,6 +4044,16 @@ ${elementSummary}${moreText}`;
3994
4044
  return `Interact with ${tag}`;
3995
4045
  }
3996
4046
  addTestAssertion() {
4047
+ if (!this.testRecording)
4048
+ return;
4049
+ this._inHaltijaUI = true;
4050
+ try {
4051
+ this._addTestAssertionImpl();
4052
+ } finally {
4053
+ this._inHaltijaUI = false;
4054
+ }
4055
+ }
4056
+ _addTestAssertionImpl() {
3997
4057
  if (!this.testRecording)
3998
4058
  return;
3999
4059
  const type = prompt(`What to check?
@@ -4068,6 +4128,14 @@ ${elementSummary}${moreText}`;
4068
4128
  }
4069
4129
  }
4070
4130
  saveTest() {
4131
+ this._inHaltijaUI = true;
4132
+ try {
4133
+ this._saveTestImpl();
4134
+ } finally {
4135
+ this._inHaltijaUI = false;
4136
+ }
4137
+ }
4138
+ _saveTestImpl() {
4071
4139
  if (!this.testRecording || this.testRecording.steps.length === 0) {
4072
4140
  alert("No steps recorded!");
4073
4141
  return;
@@ -5019,9 +5087,80 @@ ${elementSummary}${moreText}`;
5019
5087
  case "interaction":
5020
5088
  this.handleInteractionMessage(msg2);
5021
5089
  break;
5090
+ case "dialog":
5091
+ this.handleDialogMessage(msg2);
5092
+ break;
5093
+ case "video":
5094
+ this.handleVideoMessage(msg2);
5095
+ break;
5022
5096
  }
5023
5097
  this.render();
5024
5098
  }
5099
+ handleDialogMessage(msg2) {
5100
+ const { action: action2, payload: payload2 } = msg2;
5101
+ if (action2 === "configure") {
5102
+ if (payload2.alert !== undefined)
5103
+ this.dialogPolicy.alert = payload2.alert;
5104
+ if (payload2.confirm !== undefined)
5105
+ this.dialogPolicy.confirm = payload2.confirm;
5106
+ if (payload2.prompt !== undefined)
5107
+ this.dialogPolicy.prompt = payload2.prompt;
5108
+ if (payload2.beforeunload !== undefined)
5109
+ this.dialogPolicy.beforeunload = payload2.beforeunload;
5110
+ this.respond(msg2.id, true, { policy: this.dialogPolicy });
5111
+ } else if (action2 === "get-config") {
5112
+ this.respond(msg2.id, true, { policy: this.dialogPolicy });
5113
+ } else if (action2 === "history") {
5114
+ this.respond(msg2.id, true, { history: this.dialogHistory });
5115
+ } else {
5116
+ this.respond(msg2.id, false, undefined, `Unknown dialog action: ${action2}`);
5117
+ }
5118
+ }
5119
+ handleVideoMessage(msg2) {
5120
+ const { action: action2, payload: payload2 } = msg2;
5121
+ const haltija = window.haltija;
5122
+ if (action2 === "start") {
5123
+ if (!haltija?.startVideoCapture) {
5124
+ this.respond(msg2.id, false, undefined, "Video capture requires the Haltija Desktop app. Run: npx haltija@latest -f");
5125
+ return;
5126
+ }
5127
+ haltija.startVideoCapture({ maxDuration: payload2.maxDuration }).then((result2) => {
5128
+ if (result2.success) {
5129
+ this.respond(msg2.id, true, { recordingId: result2.recordingId });
5130
+ } else {
5131
+ this.respond(msg2.id, false, undefined, result2.error || "Failed to start video");
5132
+ }
5133
+ }).catch((err) => {
5134
+ this.respond(msg2.id, false, undefined, `Failed to start video: ${err.message}`);
5135
+ });
5136
+ } else if (action2 === "stop") {
5137
+ if (!haltija?.stopVideoCapture) {
5138
+ this.respond(msg2.id, false, undefined, "Video capture requires the Haltija Desktop app. Run: npx haltija@latest -f");
5139
+ return;
5140
+ }
5141
+ haltija.stopVideoCapture().then((result2) => {
5142
+ if (result2.success) {
5143
+ this.respond(msg2.id, true, { path: result2.path, duration: result2.duration, size: result2.size, format: result2.format });
5144
+ } else {
5145
+ this.respond(msg2.id, false, undefined, result2.error || "Failed to stop video");
5146
+ }
5147
+ }).catch((err) => {
5148
+ this.respond(msg2.id, false, undefined, `Failed to stop video: ${err.message}`);
5149
+ });
5150
+ } else if (action2 === "status") {
5151
+ if (!haltija?.videoStatus) {
5152
+ this.respond(msg2.id, true, { recording: false });
5153
+ return;
5154
+ }
5155
+ haltija.videoStatus().then((result2) => {
5156
+ this.respond(msg2.id, true, result2);
5157
+ }).catch(() => {
5158
+ this.respond(msg2.id, true, { recording: false });
5159
+ });
5160
+ } else {
5161
+ this.respond(msg2.id, false, undefined, `Unknown video action: ${action2}`);
5162
+ }
5163
+ }
5025
5164
  handleSemanticMessage(msg2) {
5026
5165
  const { action: action2, payload: payload2 } = msg2;
5027
5166
  if (action2 === "start" || action2 === "watch") {
@@ -5168,16 +5307,23 @@ ${elementSummary}${moreText}`;
5168
5307
  if (action2 === "refresh") {
5169
5308
  if (payload2.soft) {
5170
5309
  location.reload();
5310
+ this.respond(msg2.id, true);
5171
5311
  } else {
5172
- if (location.protocol === "blob:" || location.protocol === "data:") {
5173
- location.reload();
5312
+ const haltija = window.haltija;
5313
+ if (haltija?.hardRefresh) {
5314
+ haltija.hardRefresh().then(() => this.respond(msg2.id, true)).catch(() => {
5315
+ location.reload();
5316
+ this.respond(msg2.id, true);
5317
+ });
5318
+ return;
5319
+ }
5320
+ if (typeof caches !== "undefined") {
5321
+ caches.keys().then((names) => Promise.all(names.map((n) => caches.delete(n)))).then(() => location.reload()).catch(() => location.reload());
5174
5322
  } else {
5175
- const url = new URL(location.href);
5176
- url.searchParams.set("_haltija_refresh", Date.now().toString());
5177
- location.href = url.toString();
5323
+ location.reload();
5178
5324
  }
5325
+ this.respond(msg2.id, true);
5179
5326
  }
5180
- this.respond(msg2.id, true);
5181
5327
  } else if (action2 === "goto") {
5182
5328
  const haltija = window.haltija;
5183
5329
  if (haltija?.navigate) {
@@ -5238,20 +5384,32 @@ ${elementSummary}${moreText}`;
5238
5384
  const req = payload2;
5239
5385
  try {
5240
5386
  if (req.all) {
5241
- const elements = resolveSelectorAll(req.selector);
5387
+ const { elements, error } = resolveRefOrSelectorAll(req.ref, req.selector);
5388
+ if (error && elements.length === 0) {
5389
+ this.respond(msg2.id, false, null, error);
5390
+ return;
5391
+ }
5242
5392
  this.respond(msg2.id, true, elements.map(extractElement));
5243
5393
  } else {
5244
- const el = resolveSelector(req.selector);
5245
- this.respond(msg2.id, true, el ? extractElement(el) : null);
5394
+ const { element, targetDesc, error } = resolveRefOrSelector(req.ref, req.selector);
5395
+ if (error) {
5396
+ this.respond(msg2.id, false, null, error);
5397
+ return;
5398
+ }
5399
+ this.respond(msg2.id, true, element ? extractElement(element) : null);
5246
5400
  }
5247
5401
  } catch (err) {
5248
5402
  this.respond(msg2.id, false, null, err.message);
5249
5403
  }
5250
5404
  } else if (action2 === "inspect") {
5251
5405
  try {
5252
- const el = resolveSelector(payload2.selector);
5406
+ const { element: el, targetDesc, error } = resolveRefOrSelector(payload2.ref, payload2.selector);
5407
+ if (error) {
5408
+ this.respond(msg2.id, false, null, error);
5409
+ return;
5410
+ }
5253
5411
  if (!el) {
5254
- this.respond(msg2.id, false, null, `Element not found: ${payload2.selector}`);
5412
+ this.respond(msg2.id, false, null, `Element not found: ${targetDesc}`);
5255
5413
  return;
5256
5414
  }
5257
5415
  const opts = {
@@ -5264,7 +5422,11 @@ ${elementSummary}${moreText}`;
5264
5422
  }
5265
5423
  } else if (action2 === "inspectAll") {
5266
5424
  try {
5267
- const elements = resolveSelectorAll(payload2.selector);
5425
+ const { elements, error } = resolveRefOrSelectorAll(payload2.ref, payload2.selector);
5426
+ if (error && elements.length === 0) {
5427
+ this.respond(msg2.id, false, null, error);
5428
+ return;
5429
+ }
5268
5430
  const opts = {
5269
5431
  fullStyles: payload2.fullStyles,
5270
5432
  matchedRules: payload2.matchedRules
@@ -5276,9 +5438,13 @@ ${elementSummary}${moreText}`;
5276
5438
  }
5277
5439
  } else if (action2 === "highlight") {
5278
5440
  try {
5279
- const el = resolveSelector(payload2.selector);
5441
+ const { element: el, targetDesc, error } = resolveRefOrSelector(payload2.ref, payload2.selector);
5442
+ if (error) {
5443
+ this.respond(msg2.id, false, null, error);
5444
+ return;
5445
+ }
5280
5446
  if (!el) {
5281
- this.respond(msg2.id, false, null, `Element not found: ${payload2.selector}`);
5447
+ this.respond(msg2.id, false, null, `Element not found: ${targetDesc}`);
5282
5448
  return;
5283
5449
  }
5284
5450
  if (payload2.duration) {
@@ -5286,7 +5452,7 @@ ${elementSummary}${moreText}`;
5286
5452
  } else {
5287
5453
  showHighlight(el, payload2.label, payload2.color);
5288
5454
  }
5289
- this.respond(msg2.id, true, { highlighted: payload2.selector });
5455
+ this.respond(msg2.id, true, { highlighted: targetDesc });
5290
5456
  } catch (err) {
5291
5457
  this.respond(msg2.id, false, null, err.message);
5292
5458
  }
@@ -5413,10 +5579,21 @@ ${elementSummary}${moreText}`;
5413
5579
  img.src = dataUrl;
5414
5580
  });
5415
5581
  };
5582
+ let targetSelector = payload2?.selector;
5583
+ if (payload2?.ref) {
5584
+ const { element, targetDesc, error } = resolveRefOrSelector(payload2.ref, undefined);
5585
+ if (error) {
5586
+ this.respond(msg2.id, false, null, error);
5587
+ return;
5588
+ }
5589
+ if (element) {
5590
+ targetSelector = element.id ? `#${element.id}` : element.getAttribute("data-testid") ? `[data-testid="${element.getAttribute("data-testid")}"]` : undefined;
5591
+ }
5592
+ }
5416
5593
  const haltija = window.haltija;
5417
5594
  if (haltija?.capturePage) {
5418
5595
  try {
5419
- const capturePromise = payload2?.selector ? haltija.captureElement(payload2.selector) : haltija.capturePage();
5596
+ const capturePromise = targetSelector ? haltija.captureElement(targetSelector) : haltija.capturePage();
5420
5597
  const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve({ success: false, error: "Screenshot capture timed out after 10s" }), 1e4));
5421
5598
  const result2 = await Promise.race([capturePromise, timeoutPromise]);
5422
5599
  if (result2?.success && result2.data) {
@@ -5439,36 +5616,7 @@ ${elementSummary}${moreText}`;
5439
5616
  return;
5440
5617
  }
5441
5618
  }
5442
- const html2canvas = window.html2canvas;
5443
- if (html2canvas) {
5444
- const target = payload2?.selector ? resolveSelector(payload2.selector) : document.body;
5445
- if (!target) {
5446
- this.respond(msg2.id, false, null, `Element not found: ${payload2?.selector}`);
5447
- return;
5448
- }
5449
- const canvas = await html2canvas(target, {
5450
- useCORS: true,
5451
- allowTaint: true,
5452
- logging: false,
5453
- scale: payload2?.scale || 1
5454
- });
5455
- const dataUrl = canvas.toDataURL(mimeType, quality);
5456
- this.respond(msg2.id, true, {
5457
- image: dataUrl,
5458
- viewport,
5459
- format,
5460
- width: canvas.width,
5461
- height: canvas.height,
5462
- source: "html2canvas"
5463
- });
5464
- } else {
5465
- this.respond(msg2.id, true, {
5466
- viewport,
5467
- image: null,
5468
- note: "Screenshot capture requires the Haltija Desktop app.",
5469
- source: "viewport-only"
5470
- });
5471
- }
5619
+ this.respond(msg2.id, false, null, "Screenshots require the Haltija Desktop app. Run: npx haltija@latest -f");
5472
5620
  } catch (err) {
5473
5621
  this.respond(msg2.id, false, null, err.message);
5474
5622
  }
@@ -6936,6 +7084,67 @@ ${elementSummary}${moreText}`;
6936
7084
  }
6937
7085
  this.respond(responseId, true);
6938
7086
  }
7087
+ interceptDialogs() {
7088
+ this.originalDialogs.alert = window.alert.bind(window);
7089
+ this.originalDialogs.confirm = window.confirm.bind(window);
7090
+ this.originalDialogs.prompt = window.prompt.bind(window);
7091
+ const self = this;
7092
+ window.alert = function(message) {
7093
+ if (self._inHaltijaUI)
7094
+ return self.originalDialogs.alert(message);
7095
+ const msg2 = String(message ?? "");
7096
+ const entry = { type: "alert", message: msg2, response: undefined, timestamp: Date.now() };
7097
+ self.dialogHistory.push(entry);
7098
+ if (self.dialogHistory.length > 50)
7099
+ self.dialogHistory = self.dialogHistory.slice(-25);
7100
+ self.send("dialog", "opened", { type: "alert", message: msg2, windowId: self.windowId });
7101
+ };
7102
+ window.confirm = function(message) {
7103
+ if (self._inHaltijaUI)
7104
+ return self.originalDialogs.confirm(message);
7105
+ const msg2 = String(message ?? "");
7106
+ const accept = self.dialogPolicy.confirm === "accept";
7107
+ const entry = { type: "confirm", message: msg2, response: accept, timestamp: Date.now() };
7108
+ self.dialogHistory.push(entry);
7109
+ if (self.dialogHistory.length > 50)
7110
+ self.dialogHistory = self.dialogHistory.slice(-25);
7111
+ self.send("dialog", "opened", {
7112
+ type: "confirm",
7113
+ message: msg2,
7114
+ response: accept,
7115
+ windowId: self.windowId
7116
+ });
7117
+ return accept;
7118
+ };
7119
+ window.prompt = function(message, defaultValue) {
7120
+ if (self._inHaltijaUI)
7121
+ return self.originalDialogs.prompt(message, defaultValue);
7122
+ const msg2 = String(message ?? "");
7123
+ const policy = self.dialogPolicy.prompt;
7124
+ const response = policy === "dismiss" ? null : policy.response;
7125
+ const entry = { type: "prompt", message: msg2, defaultValue, response, timestamp: Date.now() };
7126
+ self.dialogHistory.push(entry);
7127
+ if (self.dialogHistory.length > 50)
7128
+ self.dialogHistory = self.dialogHistory.slice(-25);
7129
+ self.send("dialog", "opened", {
7130
+ type: "prompt",
7131
+ message: msg2,
7132
+ defaultValue,
7133
+ response,
7134
+ windowId: self.windowId
7135
+ });
7136
+ return response;
7137
+ };
7138
+ }
7139
+ restoreDialogs() {
7140
+ if (this.originalDialogs.alert)
7141
+ window.alert = this.originalDialogs.alert;
7142
+ if (this.originalDialogs.confirm)
7143
+ window.confirm = this.originalDialogs.confirm;
7144
+ if (this.originalDialogs.prompt)
7145
+ window.prompt = this.originalDialogs.prompt;
7146
+ this.originalDialogs = {};
7147
+ }
6939
7148
  interceptConsole() {
6940
7149
  const levels = [
6941
7150
  "log",
@@ -6994,6 +7203,7 @@ ${elementSummary}${moreText}`;
6994
7203
  currentTagName = TAG_NAME;
6995
7204
  window.__haltija_resolveSelector = resolveSelector;
6996
7205
  window.__haltija_resolveSelectorAll = resolveSelectorAll;
7206
+ window.__haltija_refRegistry = refRegistry;
6997
7207
  }
6998
7208
  registerDevChannel();
6999
7209
  var WIDGET_ID = "haltija-widget";
@@ -24,11 +24,29 @@ contextBridge.exposeInMainWorld('haltija', {
24
24
  console.log('[Haltija] navigate called:', url)
25
25
  return ipcRenderer.invoke('navigate-url', url)
26
26
  },
27
- // Create a new agent tab and return its info
27
+ // Tab management routed through main process to renderer
28
+ openTab: (url) => ipcRenderer.invoke('open-tab', url),
29
+ closeTab: (windowId) => ipcRenderer.invoke('close-tab', windowId),
30
+ focusTab: (windowId) => ipcRenderer.invoke('focus-tab', windowId),
28
31
  openAgentTab: () => {
29
32
  console.log('[Haltija] openAgentTab called')
30
33
  return ipcRenderer.invoke('open-agent-tab')
31
34
  },
35
+ // Hard refresh — bypasses all caches (CSS, JS, images, everything)
36
+ hardRefresh: () => {
37
+ console.log('[Haltija] hardRefresh called')
38
+ return ipcRenderer.invoke('hard-refresh')
39
+ },
40
+ // Video capture — forwarded to renderer via main process
41
+ startVideoCapture: (opts) => {
42
+ return ipcRenderer.invoke('video-start', opts || {})
43
+ },
44
+ stopVideoCapture: () => {
45
+ return ipcRenderer.invoke('video-stop')
46
+ },
47
+ videoStatus: () => {
48
+ return ipcRenderer.invoke('video-status')
49
+ },
32
50
  })
33
51
 
34
52
  console.log('[Haltija] Webview preload complete, window.haltija exposed')