tui-td 0.2.21 → 0.2.22

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcd0909e5284e4bdd97bd38154ba64108c9c6e95903ca067126b8fb16215ae28
4
- data.tar.gz: 9b2bf1430a37e58a72c8f185adbbfb500a70f78a3a80bb66e083dee3e9124aa8
3
+ metadata.gz: 01ea7851a70d867ca80389e3c824d6fb6b5874c6d24b5f8faf26b5daa23ee50c
4
+ data.tar.gz: 5a71d54ef5c978d300ee9d912852a98c04c03489270e06241fea2236b7d6c81b
5
5
  SHA512:
6
- metadata.gz: 4c084254da02d47fe56471eab086e89f35025c875153e0987b8399de324894a270a7488992c64ad78aa888c7886810898b0365b1a288667caef4c17f68096b82
7
- data.tar.gz: 97aafa9d1056b7db02f84cb9b378edfb869e0651bfebaeceedfcb0f75d05ad76f97994a2986af80e84d8d8cbf77c9e0641600860247e771bb58b404b4aabd492
6
+ metadata.gz: bd5f7d8a501a056e40033d1fd97f99c1fa15b3e6c8a352836acd5ab1e899d0d65f98c9c7e0d0b12fb13c1c199ade39b041ff292164094e5bd894593ff0f31afd
7
+ data.tar.gz: bf225f89e3270971bbbe0968a6bab9b7cb6761a5746d935211f1f6843dca48940917360feb69bed43e3a565d7a2aefe8f6e45b9d1a7ead94855a0f094bc67fdc
data/CHANGELOG.md CHANGED
@@ -1,6 +1,39 @@
1
1
  # CHANGELOG
2
2
 
3
- ## 0.2.20
3
+ ## 0.2.22
4
+
5
+ ### Added
6
+
7
+ - **tans-parser 0.1.5 integration**: Confidence scoring for UI element detection
8
+ - `Element#confidence` attribute (0.0–1.0) — higher values indicate more reliable detection
9
+ - `Element#confident?` — returns true when confidence ≥ 0.5 or nil
10
+ - RSpec matchers: `min_confidence:` keyword on `have_button`, `have_dialog`,
11
+ `have_checkbox`, `have_input`, `have_label`, `have_menu`, `have_tab`,
12
+ `have_statusbar`, `have_progress_bar`, `have_role`
13
+ - JSON test steps: `min_confidence` field on all `assert_*` role steps
14
+ - MCP `tui_find_elements`: `min_confidence` argument and `[conf=...]` in output
15
+ - Minitest assertions: `min_confidence:` keyword on all selector-based assertions
16
+ - Confidence attribute visible in `Element#to_h`
17
+
18
+ ### Changed
19
+
20
+ - Reduced false positives from tans-parser 0.1.5 (numeric brackets, short progress bars,
21
+ digit-colon patterns, URL schemes are now excluded)
22
+ - Dialog detection confidence boosted for titled borders (+0.05)
23
+ - Multi-word labels get higher confidence (0.85 vs 0.8)
24
+ - Tab confidence varies by count (≥3 tabs → 0.85, otherwise 0.7)
25
+ - `TestRunner#check_role` includes confidence scores in result messages
26
+ - Minitest assertions pass through `min_confidence` to element detection
27
+
28
+ ### Testing
29
+
30
+ - 8 new confidence-specific spec tests (matchers_spec.rb)
31
+ - All 440 RSpec tests pass, all smoke tests pass (91/91 MCP, 11/11 Minitest)
32
+ - SimpleCov coverage tracking with minimum threshold (84.0%)
33
+
34
+ ## 0.2.20 (yanked — incorrect tans-parser dependency)
35
+
36
+ ## 0.2.21
4
37
 
5
38
  ### Added
6
39
 
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ # Check for new tans-parser versions and integrate if found
3
+ # Called by crontab every 15 minutes
4
+
5
+ set -e
6
+
7
+ cd /Users/halukdurmus/Development/tui-td
8
+
9
+ # Get the latest tans-parser version
10
+ LATEST=$(gem search tans-parser --remote --exact 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -1)
11
+ CURRENT=$(grep "tans-parser" Gemfile.lock | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
12
+
13
+ echo "$(date): Latest tans-parser: $LATEST, Current: $CURRENT"
14
+
15
+ if [ "$LATEST" != "$CURRENT" ] && [ -n "$LATEST" ]; then
16
+ echo "$(date): New version $LATEST detected! Triggering integration..."
17
+ # Write a trigger file for the next claude session to pick up
18
+ echo "$LATEST" > /tmp/tui_td_tans_parser_update_trigger
19
+ fi
20
+
21
+ exit 0
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/ModuleLength, Metrics/BlockLength, Metrics/ParameterLists, Layout/LineLength
3
+ # rubocop:disable Metrics/ModuleLength, Metrics/ParameterLists, Layout/LineLength, Metrics/BlockLength
4
4
 
5
5
  require "rspec/expectations"
6
6
 
@@ -167,29 +167,58 @@ module TUITD
167
167
 
168
168
  # Selector-based matchers — work with both State and Driver (auto-wait)
169
169
 
170
- RSpec::Matchers.define :have_button do |expected|
170
+ RSpec::Matchers.define :have_button do |expected, min_confidence: nil|
171
171
  match do |actual|
172
172
  Matchers.auto_wait(actual) do |s|
173
- Selector.new(s).button(text: expected)
173
+ el = Selector.new(s).button(text: expected)
174
+ if min_confidence && el
175
+ el.confidence && el.confidence >= min_confidence
176
+ else
177
+ el
178
+ end
174
179
  end
175
180
  end
176
181
 
177
- description { "have button #{expected.inspect}" }
178
- failure_message { |_actual| "expected terminal to have a button #{expected.inspect}" }
179
- failure_message_when_negated { |_actual| "expected terminal NOT to have a button #{expected.inspect}" }
182
+ description do
183
+ desc = "have button #{expected.inspect}"
184
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
185
+ desc
186
+ end
187
+ failure_message do |_actual|
188
+ desc = "expected terminal to have a button #{expected.inspect}"
189
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
190
+ desc
191
+ end
192
+ failure_message_when_negated do |_actual|
193
+ "expected terminal NOT to have a button #{expected.inspect}"
194
+ end
180
195
  end
181
196
 
182
- RSpec::Matchers.define :have_dialog do
197
+ RSpec::Matchers.define :have_dialog do |min_confidence: nil|
183
198
  match do |actual|
184
- Matchers.auto_wait(actual) { |s| Selector.new(s).dialogs.any? }
199
+ Matchers.auto_wait(actual) do |s|
200
+ elements = Selector.new(s).dialogs
201
+ elements = elements.select { |e| e.confidence && e.confidence >= min_confidence } if min_confidence
202
+ elements.any?
203
+ end
185
204
  end
186
205
 
187
- description { "have a dialog" }
188
- failure_message { |_actual| "expected terminal to have a dialog" }
189
- failure_message_when_negated { |_actual| "expected terminal NOT to have a dialog" }
206
+ description do
207
+ desc = "have a dialog"
208
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
209
+ desc
210
+ end
211
+ failure_message do |_actual|
212
+ desc = "expected terminal to have a dialog"
213
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
214
+ desc
215
+ end
216
+ failure_message_when_negated do |_actual|
217
+ "expected terminal NOT to have a dialog"
218
+ end
190
219
  end
191
220
 
192
- RSpec::Matchers.define :have_checkbox do |expected|
221
+ RSpec::Matchers.define :have_checkbox do |expected, min_confidence: nil|
193
222
  chain(:checked) { @checked = true }
194
223
  chain(:unchecked) { @checked = false }
195
224
 
@@ -197,7 +226,12 @@ module TUITD
197
226
  Matchers.auto_wait(actual) do |s|
198
227
  filters = { text: expected }
199
228
  filters[:checked] = @checked unless @checked.nil?
200
- Selector.new(s).checkbox(**filters)
229
+ el = Selector.new(s).checkbox(**filters)
230
+ if min_confidence && el
231
+ el.confidence && el.confidence >= min_confidence
232
+ else
233
+ el
234
+ end
201
235
  end
202
236
  end
203
237
 
@@ -205,12 +239,14 @@ module TUITD
205
239
  desc = "have checkbox #{expected.inspect}"
206
240
  desc += " (checked)" if @checked == true
207
241
  desc += " (unchecked)" if @checked == false
242
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
208
243
  desc
209
244
  end
210
245
  failure_message do |_actual|
211
246
  desc = "expected terminal to have checkbox #{expected.inspect}"
212
247
  desc += " (checked)" if @checked == true
213
248
  desc += " (unchecked)" if @checked == false
249
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
214
250
  desc
215
251
  end
216
252
  failure_message_when_negated do |_actual|
@@ -220,15 +256,16 @@ module TUITD
220
256
  desc
221
257
  end
222
258
  end
223
-
224
- RSpec::Matchers.define :have_role do |role, text: nil, checked: nil, disabled: nil|
259
+ RSpec::Matchers.define :have_role do |role, text: nil, checked: nil, disabled: nil, min_confidence: nil|
225
260
  match do |actual|
226
261
  Matchers.auto_wait(actual) do |s|
227
262
  filters = {}
228
263
  filters[:text] = text if text
229
264
  filters[:checked] = checked unless checked.nil?
230
265
  filters[:disabled] = disabled unless disabled.nil?
231
- Selector.new(s).get_by_role(role, **filters).any?
266
+ elements = Selector.new(s).get_by_role(role, **filters)
267
+ elements = elements.select { |e| e.confidence && e.confidence >= min_confidence } if min_confidence
268
+ elements.any?
232
269
  end
233
270
  end
234
271
 
@@ -237,6 +274,7 @@ module TUITD
237
274
  desc += " with text #{text.inspect}" if text
238
275
  desc += " (checked)" if checked == true
239
276
  desc += " (disabled)" if disabled == true
277
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
240
278
  desc
241
279
  end
242
280
  failure_message do |_actual|
@@ -244,30 +282,37 @@ module TUITD
244
282
  desc += " with text #{text.inspect}" if text
245
283
  desc += " (checked)" if checked == true
246
284
  desc += " (disabled)" if disabled == true
285
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
247
286
  desc
248
287
  end
249
288
  failure_message_when_negated do |_actual|
250
289
  desc = "expected terminal NOT to have a :#{role}"
251
290
  desc += " with text #{text.inspect}" if text
291
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
252
292
  desc
253
293
  end
254
294
  end
295
+ # rubocop:enable Metrics/BlockLength
255
296
 
256
- # New role matchers (tans-parser 0.1.2)
297
+ # New role matchers (tans-parser 0.1.2+) with confidence support (0.1.5+)
257
298
 
258
- RSpec::Matchers.define :have_input do |expected = nil|
299
+ RSpec::Matchers.define :have_input do |expected = nil, min_confidence: nil|
259
300
  match do |actual|
260
301
  Matchers.auto_wait(actual) do |s|
261
302
  if expected
262
- Selector.new(s).input(text: expected)
303
+ el = Selector.new(s).input(text: expected)
304
+ min_confidence ? (el&.confidence && el.confidence >= min_confidence) : el
263
305
  else
264
- Selector.new(s).inputs.any?
306
+ elements = Selector.new(s).inputs
307
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
265
308
  end
266
309
  end
267
310
  end
268
311
 
269
312
  description do
270
- expected ? "have input #{expected.inspect}" : "have an input field"
313
+ desc = expected ? "have input #{expected.inspect}" : "have an input field"
314
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
315
+ desc
271
316
  end
272
317
  failure_message do |_actual|
273
318
  expected ? "expected terminal to have an input #{expected.inspect}" : "expected terminal to have an input field"
@@ -277,19 +322,18 @@ module TUITD
277
322
  end
278
323
  end
279
324
 
280
- RSpec::Matchers.define :have_label do |expected = nil|
325
+ RSpec::Matchers.define :have_label do |expected = nil, min_confidence: nil|
281
326
  match do |actual|
282
327
  Matchers.auto_wait(actual) do |s|
283
- if expected
284
- Selector.new(s).label(text: expected)
285
- else
286
- Selector.new(s).labels.any?
287
- end
328
+ elements = expected ? [Selector.new(s).label(text: expected)].compact : Selector.new(s).labels
329
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
288
330
  end
289
331
  end
290
332
 
291
333
  description do
292
- expected ? "have label #{expected.inspect}" : "have a label"
334
+ desc = expected ? "have label #{expected.inspect}" : "have a label"
335
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
336
+ desc
293
337
  end
294
338
  failure_message do |_actual|
295
339
  expected ? "expected terminal to have a label #{expected.inspect}" : "expected terminal to have a label"
@@ -299,19 +343,18 @@ module TUITD
299
343
  end
300
344
  end
301
345
 
302
- RSpec::Matchers.define :have_menu do |expected = nil|
346
+ RSpec::Matchers.define :have_menu do |expected = nil, min_confidence: nil|
303
347
  match do |actual|
304
348
  Matchers.auto_wait(actual) do |s|
305
- if expected
306
- Selector.new(s).menu(text: expected)
307
- else
308
- Selector.new(s).menus.any?
309
- end
349
+ elements = expected ? [Selector.new(s).menu(text: expected)].compact : Selector.new(s).menus
350
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
310
351
  end
311
352
  end
312
353
 
313
354
  description do
314
- expected ? "have menu #{expected.inspect}" : "have a menu"
355
+ desc = expected ? "have menu #{expected.inspect}" : "have a menu"
356
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
357
+ desc
315
358
  end
316
359
  failure_message do |_actual|
317
360
  expected ? "expected terminal to have a menu #{expected.inspect}" : "expected terminal to have a menu"
@@ -321,19 +364,18 @@ module TUITD
321
364
  end
322
365
  end
323
366
 
324
- RSpec::Matchers.define :have_tab do |expected = nil|
367
+ RSpec::Matchers.define :have_tab do |expected = nil, min_confidence: nil|
325
368
  match do |actual|
326
369
  Matchers.auto_wait(actual) do |s|
327
- if expected
328
- Selector.new(s).tab(text: expected)
329
- else
330
- Selector.new(s).tabs.any?
331
- end
370
+ elements = expected ? [Selector.new(s).tab(text: expected)].compact : Selector.new(s).tabs
371
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
332
372
  end
333
373
  end
334
374
 
335
375
  description do
336
- expected ? "have tab #{expected.inspect}" : "have a tab"
376
+ desc = expected ? "have tab #{expected.inspect}" : "have a tab"
377
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
378
+ desc
337
379
  end
338
380
  failure_message do |_actual|
339
381
  expected ? "expected terminal to have a tab #{expected.inspect}" : "expected terminal to have a tab"
@@ -343,19 +385,18 @@ module TUITD
343
385
  end
344
386
  end
345
387
 
346
- RSpec::Matchers.define :have_statusbar do |expected = nil|
388
+ RSpec::Matchers.define :have_statusbar do |expected = nil, min_confidence: nil|
347
389
  match do |actual|
348
390
  Matchers.auto_wait(actual) do |s|
349
- if expected
350
- Selector.new(s).statusbar(text: expected)
351
- else
352
- Selector.new(s).statusbars.any?
353
- end
391
+ elements = expected ? [Selector.new(s).statusbar(text: expected)].compact : Selector.new(s).statusbars
392
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
354
393
  end
355
394
  end
356
395
 
357
396
  description do
358
- expected ? "have status bar #{expected.inspect}" : "have a status bar"
397
+ desc = expected ? "have status bar #{expected.inspect}" : "have a status bar"
398
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
399
+ desc
359
400
  end
360
401
  failure_message do |_actual|
361
402
  expected ? "expected terminal to have a status bar #{expected.inspect}" : "expected terminal to have a status bar"
@@ -365,19 +406,23 @@ module TUITD
365
406
  end
366
407
  end
367
408
 
368
- RSpec::Matchers.define :have_progress_bar do |expected = nil|
409
+ RSpec::Matchers.define :have_progress_bar do |expected = nil, min_confidence: nil|
369
410
  match do |actual|
370
411
  Matchers.auto_wait(actual) do |s|
371
412
  if expected
372
- Selector.new(s).progress_bar(text: expected)
413
+ el = Selector.new(s).progress_bar(text: expected)
414
+ min_confidence ? (el&.confidence && el.confidence >= min_confidence) : el
373
415
  else
374
- Selector.new(s).progress_bars.any?
416
+ elements = Selector.new(s).progress_bars
417
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
375
418
  end
376
419
  end
377
420
  end
378
421
 
379
422
  description do
380
- expected ? "have progress bar #{expected.inspect}" : "have a progress bar"
423
+ desc = expected ? "have progress bar #{expected.inspect}" : "have a progress bar"
424
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
425
+ desc
381
426
  end
382
427
  failure_message do |_actual|
383
428
  expected ? "expected terminal to have a progress bar #{expected.inspect}" : "expected terminal to have a progress bar"
@@ -386,7 +431,6 @@ module TUITD
386
431
  expected ? "expected terminal NOT to have a progress bar #{expected.inspect}" : "expected terminal NOT to have a progress bar"
387
432
  end
388
433
  end
389
-
390
434
  # Snapshot comparison matcher — works with both State and Driver (auto-wait).
391
435
  # Snapshot comparison matcher — supports both named (disk-based) and
392
436
  # legacy in-memory State objects.
@@ -402,14 +446,15 @@ module TUITD
402
446
  # pre = driver.snapshot
403
447
  # expect(driver).to match_snapshot(pre, chars_only: true)
404
448
  #
449
+ # rubocop:disable Metrics/BlockLength
405
450
  RSpec::Matchers.define :match_snapshot do |ref, type: nil, wait: false, chars_only: nil, ignore_rows: nil, region: nil|
406
451
  match do |actual|
407
452
  # Normalize type: backward compat for chars_only parameter
408
- effective_type = type
409
- if effective_type.nil? && !chars_only.nil?
410
- effective_type = chars_only ? :text : :full
453
+ @effective_type = type
454
+ if @effective_type.nil? && !chars_only.nil?
455
+ @effective_type = chars_only ? :text : :full
411
456
  end
412
- effective_type ||= :text
457
+ @effective_type ||= :text
413
458
 
414
459
  @snapshot_name = nil
415
460
  @diff_result = nil
@@ -417,7 +462,7 @@ module TUITD
417
462
  # Legacy path: snapshot_ref is a State object (responds to diff)
418
463
  if ref.respond_to?(:diff)
419
464
  Matchers.auto_wait(actual) do |s|
420
- chars = effective_type == :text
465
+ chars = @effective_type == :text
421
466
  diffs = ref.diff(s, chars_only: chars)
422
467
  diffs.select! { |d| Array(region).include?(d[:row]) } if region
423
468
  diffs.reject! { |d| Array(ignore_rows).include?(d[:row]) } if ignore_rows
@@ -427,7 +472,7 @@ module TUITD
427
472
  else
428
473
  # Named snapshot path
429
474
  @snapshot_name = ref.to_s
430
- snap = Snapshot.new(@snapshot_name, type: effective_type)
475
+ snap = Snapshot.new(@snapshot_name, type: @effective_type)
431
476
 
432
477
  # Get state_data from actual
433
478
  if wait && actual.respond_to?(:wait_for_stable)
@@ -450,8 +495,8 @@ module TUITD
450
495
  true
451
496
  elsif !snap.exists?
452
497
  snap.save(state_data)
453
- @diff_result = TUITD::Snapshot::ComparisonResult.new(passed: true, diff_count: 0, type: effective_type,
454
- message: "Snapshot '#{@snapshot_name}' created (#{effective_type})",)
498
+ @diff_result = TUITD::Snapshot::ComparisonResult.new(passed: true, diff_count: 0, type: @effective_type,
499
+ message: "Snapshot '#{@snapshot_name}' created (#{@effective_type})",)
455
500
  true
456
501
  else
457
502
  @diff_result = snap.compare(state_data, ignore_rows: ignore_rows, region: region)
@@ -462,10 +507,10 @@ module TUITD
462
507
 
463
508
  description do
464
509
  if @snapshot_name
465
- "match snapshot #{@snapshot_name.inspect} (type: #{effective_type})"
510
+ "match snapshot #{@snapshot_name.inspect} (type: #{@effective_type})"
466
511
  else
467
- desc = "match snapshot"
468
- desc << " (chars only)" if effective_type == :text
512
+ desc = +"match snapshot"
513
+ desc << " (chars only)" if @effective_type == :text
469
514
  desc
470
515
  end
471
516
  end
@@ -488,4 +533,4 @@ module TUITD
488
533
  end
489
534
  end
490
535
  end
491
- # rubocop:enable Metrics/ModuleLength, Metrics/BlockLength, Metrics/ParameterLists, Layout/LineLength
536
+ # rubocop:enable Metrics/ModuleLength, Metrics/ParameterLists, Layout/LineLength, Metrics/BlockLength
@@ -291,7 +291,7 @@ module TUITD
291
291
  },
292
292
  {
293
293
  name: "tui_find_elements",
294
- description: "Search for UI elements in the terminal state. Returns buttons, checkboxes, dialogs, statusbars, progress bars, inputs, labels, menus, and tabs detected by heuristic analysis. Optionally filter by role, text, checked, and/or disabled state.",
294
+ description: "Search for UI elements in the terminal state. Returns buttons, checkboxes, dialogs, statusbars, progress bars, inputs, labels, menus, and tabs detected by heuristic analysis. Optionally filter by role, text, checked, disabled state, and/or minimum confidence score (0.0-1.0). Elements include a confidence score from tans-parser 0.1.5+.",
295
295
  inputSchema: {
296
296
  type: "object",
297
297
  properties: {
@@ -311,6 +311,10 @@ module TUITD
311
311
  type: "boolean",
312
312
  description: "Filter by disabled state. Optional.",
313
313
  },
314
+ min_confidence: {
315
+ type: "number",
316
+ description: "Minimum confidence score (0.0-1.0) for element detection. Higher values reduce false positives. Default: no filter. Example: 0.8.",
317
+ },
314
318
  },
315
319
  },
316
320
  },
@@ -693,6 +697,7 @@ module TUITD
693
697
  text = args["text"]
694
698
  checked = args.key?("checked") ? args["checked"] : nil
695
699
  disabled = args.key?("disabled") ? args["disabled"] : nil
700
+ min_confidence = args["min_confidence"]
696
701
 
697
702
  filters = {}
698
703
  filters[:text] = text if text
@@ -709,11 +714,15 @@ module TUITD
709
714
  result
710
715
  end
711
716
 
717
+ # Apply confidence filter (tans-parser 0.1.5+)
718
+ elements = elements.select { |e| e.confidence && e.confidence >= min_confidence } if min_confidence
719
+
712
720
  if elements.empty?
713
721
  desc = role ? "role :#{role}" : "any role"
714
722
  desc += " with text #{text.inspect}" if text
715
723
  desc += " checked=#{checked}" unless checked.nil?
716
724
  desc += " disabled=#{disabled}" unless disabled.nil?
725
+ desc += " min_confidence=#{min_confidence}" if min_confidence
717
726
  "No elements found for #{desc}"
718
727
  else
719
728
  lines = ["Found #{elements.size} element(s):"]
@@ -725,6 +734,7 @@ module TUITD
725
734
  parts << "(checked)" if el.checked
726
735
  parts << "(disabled)" if el.disabled
727
736
  parts << "(focused)" if el.focused
737
+ parts << "[conf=#{format("%.2f", el.confidence)}]" if el.confidence
728
738
  lines << parts.join(" ")
729
739
  end
730
740
  lines.join("\n")
@@ -110,9 +110,18 @@ module TUITD
110
110
 
111
111
  # --- Selector-based ---
112
112
 
113
- def assert_button(actual, expected)
114
- result = auto_wait(actual) { |s| TUITD::Selector.new(s).button(text: expected) }
115
- assert(result, "Expected terminal to have a button #{expected.inspect}")
113
+ def assert_button(actual, expected, min_confidence: nil)
114
+ result = auto_wait(actual) do |s|
115
+ el = TUITD::Selector.new(s).button(text: expected)
116
+ if min_confidence && el
117
+ el.confidence && el.confidence >= min_confidence
118
+ else
119
+ el
120
+ end
121
+ end
122
+ msg = "Expected terminal to have a button #{expected.inspect}"
123
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
124
+ assert(result, msg)
116
125
  end
117
126
 
118
127
  def refute_button(actual, expected)
@@ -120,9 +129,14 @@ module TUITD
120
129
  assert(result, "Expected terminal NOT to have a button #{expected.inspect}")
121
130
  end
122
131
 
123
- def assert_dialog(actual)
124
- result = auto_wait(actual) { |s| TUITD::Selector.new(s).dialogs.any? }
125
- assert(result, "Expected terminal to have a dialog")
132
+ def assert_dialog(actual, min_confidence: nil)
133
+ result = auto_wait(actual) do |s|
134
+ elements = TUITD::Selector.new(s).dialogs
135
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
136
+ end
137
+ msg = "Expected terminal to have a dialog"
138
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
139
+ assert(result, msg)
126
140
  end
127
141
 
128
142
  def refute_dialog(actual)
@@ -130,107 +144,123 @@ module TUITD
130
144
  assert(result, "Expected terminal NOT to have a dialog")
131
145
  end
132
146
 
133
- def assert_checkbox(actual, expected, checked: nil, unchecked: nil)
147
+ def assert_checkbox(actual, expected, checked: nil, unchecked: nil, min_confidence: nil)
134
148
  checked = false if unchecked
135
149
  result = auto_wait(actual) do |s|
136
150
  filters = { text: expected }
137
151
  filters[:checked] = checked unless checked.nil?
138
- TUITD::Selector.new(s).checkbox(**filters)
152
+ el = TUITD::Selector.new(s).checkbox(**filters)
153
+ if min_confidence && el
154
+ el.confidence && el.confidence >= min_confidence
155
+ else
156
+ el
157
+ end
139
158
  end
140
159
  msg = "Expected terminal to have checkbox #{expected.inspect}"
141
160
  msg += " (checked)" if checked == true
142
161
  msg += " (unchecked)" if checked == false
162
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
143
163
  assert(result, msg)
144
164
  end
145
165
 
146
- def assert_role(actual, role, text: nil, checked: nil, disabled: nil)
166
+ def assert_role(actual, role, text: nil, checked: nil, disabled: nil, min_confidence: nil)
147
167
  result = auto_wait(actual) do |s|
148
168
  filters = {}
149
169
  filters[:text] = text if text
150
170
  filters[:checked] = checked unless checked.nil?
151
171
  filters[:disabled] = disabled unless disabled.nil?
152
- TUITD::Selector.new(s).get_by_role(role, **filters).any?
172
+ elements = TUITD::Selector.new(s).get_by_role(role, **filters)
173
+ if min_confidence
174
+ elements.any? { |e| e.confidence && e.confidence >= min_confidence }
175
+ else
176
+ elements.any?
177
+ end
153
178
  end
154
179
  msg = "Expected terminal to have role :#{role}"
155
180
  msg += " with text #{text.inspect}" if text
181
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
156
182
  assert(result, msg)
157
183
  end
158
184
 
159
- def assert_input(actual, expected = nil)
185
+ def assert_input(actual, expected = nil, min_confidence: nil)
160
186
  result = auto_wait(actual) do |s|
187
+ sel = TUITD::Selector.new(s)
161
188
  if expected
162
- TUITD::Selector.new(s).input(text: expected)
189
+ el = sel.input(text: expected)
190
+ min_confidence ? (el&.confidence && el.confidence >= min_confidence) : el
163
191
  else
164
- TUITD::Selector.new(s).inputs.any?
192
+ elements = sel.inputs
193
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
165
194
  end
166
195
  end
167
196
  msg = "Expected terminal to have an input field"
168
197
  msg += " #{expected.inspect}" if expected
198
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
169
199
  assert(result, msg)
170
200
  end
171
201
 
172
- def assert_label(actual, expected = nil)
202
+ def assert_label(actual, expected = nil, min_confidence: nil)
173
203
  result = auto_wait(actual) do |s|
174
- if expected
175
- TUITD::Selector.new(s).label(text: expected)
176
- else
177
- TUITD::Selector.new(s).labels.any?
178
- end
204
+ sel = TUITD::Selector.new(s)
205
+ elements = expected ? [sel.label(text: expected)].compact : sel.labels
206
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
179
207
  end
180
208
  msg = "Expected terminal to have a label"
181
209
  msg += " #{expected.inspect}" if expected
210
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
182
211
  assert(result, msg)
183
212
  end
184
213
 
185
- def assert_menu(actual, expected = nil)
214
+ def assert_menu(actual, expected = nil, min_confidence: nil)
186
215
  result = auto_wait(actual) do |s|
187
- if expected
188
- TUITD::Selector.new(s).menu(text: expected)
189
- else
190
- TUITD::Selector.new(s).menus.any?
191
- end
216
+ sel = TUITD::Selector.new(s)
217
+ elements = expected ? [sel.menu(text: expected)].compact : sel.menus
218
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
192
219
  end
193
220
  msg = "Expected terminal to have a menu"
194
221
  msg += " #{expected.inspect}" if expected
222
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
195
223
  assert(result, msg)
196
224
  end
197
225
 
198
- def assert_tab(actual, expected = nil)
226
+ def assert_tab(actual, expected = nil, min_confidence: nil)
199
227
  result = auto_wait(actual) do |s|
200
- if expected
201
- TUITD::Selector.new(s).tab(text: expected)
202
- else
203
- TUITD::Selector.new(s).tabs.any?
204
- end
228
+ sel = TUITD::Selector.new(s)
229
+ elements = expected ? [sel.tab(text: expected)].compact : sel.tabs
230
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
205
231
  end
206
232
  msg = "Expected terminal to have a tab"
207
233
  msg += " #{expected.inspect}" if expected
234
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
208
235
  assert(result, msg)
209
236
  end
210
237
 
211
- def assert_statusbar(actual, expected = nil)
238
+ def assert_statusbar(actual, expected = nil, min_confidence: nil)
212
239
  result = auto_wait(actual) do |s|
213
- if expected
214
- TUITD::Selector.new(s).statusbar(text: expected)
215
- else
216
- TUITD::Selector.new(s).statusbars.any?
217
- end
240
+ sel = TUITD::Selector.new(s)
241
+ elements = expected ? [sel.statusbar(text: expected)].compact : sel.statusbars
242
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
218
243
  end
219
244
  msg = "Expected terminal to have a status bar"
220
245
  msg += " #{expected.inspect}" if expected
246
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
221
247
  assert(result, msg)
222
248
  end
223
249
 
224
- def assert_progress_bar(actual, expected = nil)
250
+ def assert_progress_bar(actual, expected = nil, min_confidence: nil)
225
251
  result = auto_wait(actual) do |s|
252
+ sel = TUITD::Selector.new(s)
226
253
  if expected
227
- TUITD::Selector.new(s).progress_bar(text: expected)
254
+ el = sel.progress_bar(text: expected)
255
+ min_confidence ? (el&.confidence && el.confidence >= min_confidence) : el
228
256
  else
229
- TUITD::Selector.new(s).progress_bars.any?
257
+ elements = sel.progress_bars
258
+ min_confidence ? elements.any? { |e| e.confidence && e.confidence >= min_confidence } : elements.any?
230
259
  end
231
260
  end
232
261
  msg = "Expected terminal to have a progress bar"
233
262
  msg += " #{expected.inspect}" if expected
263
+ msg += " (min_confidence: #{min_confidence})" if min_confidence
234
264
  assert(result, msg)
235
265
  end
236
266
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength, Metrics/ClassLength
3
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength, Metrics/ClassLength, Metrics/ParameterLists, Layout/LineLength
4
4
 
5
5
  require "json"
6
6
  require "shellwords"
@@ -164,35 +164,43 @@ module TUITD
164
164
  end
165
165
 
166
166
  when "assert_button"
167
- check_role(driver, :button, value.to_s)
167
+ check_role(driver, :button, value.to_s, min_confidence: step[:min_confidence])
168
168
 
169
169
  when "assert_dialog"
170
- check_role(driver, :dialog, nil)
170
+ check_role(driver, :dialog, nil, min_confidence: step[:min_confidence])
171
171
 
172
172
  when "assert_checkbox"
173
- check_role(driver, :checkbox, value.to_s, checked: step[:checked], disabled: step[:disabled])
173
+ check_role(driver, :checkbox, value.to_s, checked: step[:checked],
174
+ disabled: step[:disabled], min_confidence: step[:min_confidence],)
174
175
 
175
176
  when "assert_role"
176
177
  role = step[:role]&.to_sym
177
- check_role(driver, role, value.to_s, checked: step[:checked], disabled: step[:disabled])
178
+ check_role(driver, role, value.to_s, checked: step[:checked],
179
+ disabled: step[:disabled], min_confidence: step[:min_confidence],)
178
180
 
179
181
  when "assert_input"
180
- check_role(driver, :input, value == true ? nil : value.to_s)
182
+ check_role(driver, :input, value == true ? nil : value.to_s,
183
+ min_confidence: step[:min_confidence],)
181
184
 
182
185
  when "assert_label"
183
- check_role(driver, :label, value == true ? nil : value.to_s)
186
+ check_role(driver, :label, value == true ? nil : value.to_s,
187
+ min_confidence: step[:min_confidence],)
184
188
 
185
189
  when "assert_menu"
186
- check_role(driver, :menu, value == true ? nil : value.to_s)
190
+ check_role(driver, :menu, value == true ? nil : value.to_s,
191
+ min_confidence: step[:min_confidence],)
187
192
 
188
193
  when "assert_tab"
189
- check_role(driver, :tab, value == true ? nil : value.to_s)
194
+ check_role(driver, :tab, value == true ? nil : value.to_s,
195
+ min_confidence: step[:min_confidence],)
190
196
 
191
197
  when "assert_statusbar"
192
- check_role(driver, :statusbar, value == true ? nil : value.to_s)
198
+ check_role(driver, :statusbar, value == true ? nil : value.to_s,
199
+ min_confidence: step[:min_confidence],)
193
200
 
194
201
  when "assert_progress_bar"
195
- check_role(driver, :progress, value == true ? nil : value.to_s)
202
+ check_role(driver, :progress, value == true ? nil : value.to_s,
203
+ min_confidence: step[:min_confidence],)
196
204
 
197
205
  when "snapshot"
198
206
  ensure_driver!(driver)
@@ -305,7 +313,7 @@ module TUITD
305
313
 
306
314
  private
307
315
 
308
- def check_role(driver, role, text, checked: nil, disabled: nil)
316
+ def check_role(driver, role, text, checked: nil, disabled: nil, min_confidence: nil)
309
317
  ensure_driver!(driver)
310
318
  state = State.new(driver.state_data)
311
319
  selector = Selector.new(state)
@@ -316,6 +324,8 @@ module TUITD
316
324
  filters[:disabled] = disabled unless disabled.nil?
317
325
  elements = selector.get_by_role(role, **filters)
318
326
 
327
+ elements = elements.select { |e| e.confidence && e.confidence >= min_confidence } if min_confidence
328
+
319
329
  action = "assert_#{role}"
320
330
  if elements.any?
321
331
  count = elements.size
@@ -323,11 +333,19 @@ module TUITD
323
333
  desc += " (checked)" if checked == true
324
334
  desc += " (unchecked)" if checked == false
325
335
  desc += " (disabled)" if disabled == true
326
- Result.new(step: action, passed: true, message: "Found #{count} #{desc} element(s)")
336
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
337
+ confidence_info = if min_confidence
338
+ confs = elements.map { |e| e.confidence || 0 }.sort
339
+ " [#{confs.map { |c| format("%.2f", c) }.join(", ")}]"
340
+ else
341
+ ""
342
+ end
343
+ Result.new(step: action, passed: true, message: "Found #{count} #{desc} element(s)#{confidence_info}")
327
344
  else
328
345
  desc = text ? "#{role} with text #{text.inspect}" : role.to_s
329
346
  desc += " (checked)" if checked == true
330
347
  desc += " (disabled)" if disabled == true
348
+ desc += " (min_confidence: #{min_confidence})" if min_confidence
331
349
  Result.new(step: action, passed: false, message: "No #{desc} found")
332
350
  end
333
351
  end
@@ -436,4 +454,4 @@ module TUITD
436
454
  end
437
455
  end
438
456
  end
439
- # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength, Metrics/ClassLength
457
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/BlockLength, Metrics/ClassLength, Metrics/ParameterLists, Layout/LineLength
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TUITD
4
- VERSION = "0.2.21"
4
+ VERSION = "0.2.22"
5
5
  end
data/lib/tui_td.rb CHANGED
@@ -9,19 +9,19 @@ end
9
9
  # The convenience method below reopens the module after requires, intentional for bootstrapping.
10
10
 
11
11
  require_relative "tui_td/version"
12
- require_relative "tui_td/driver"
13
- require_relative "tui_td/ansi_parser"
14
- require_relative "tui_td/ansi_utils"
15
- require_relative "tui_td/state"
16
- require_relative "tui_td/configuration"
17
- require_relative "tui_td/snapshot"
18
- require_relative "tui_td/screenshot"
19
- require_relative "tui_td/video_recorder"
20
- require_relative "tui_td/html_renderer"
21
- require_relative "tui_td/test_runner"
22
- require_relative "tui_td/selector"
23
- require_relative "tui_td/mcp/server"
24
- require_relative "tui_td/cli"
12
+ require "tui_td/driver"
13
+ require "tui_td/ansi_parser"
14
+ require "tui_td/ansi_utils"
15
+ require "tui_td/state"
16
+ require "tui_td/configuration"
17
+ require "tui_td/snapshot"
18
+ require "tui_td/screenshot"
19
+ require "tui_td/video_recorder"
20
+ require "tui_td/html_renderer"
21
+ require "tui_td/test_runner"
22
+ require "tui_td/selector"
23
+ require "tui_td/mcp/server"
24
+ require "tui_td/cli"
25
25
 
26
26
  module TUITD
27
27
  # Convenience method: start a TUI driver, capture initial state
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tui-td
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.21
4
+ version: 0.2.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Haluk Durmus
@@ -71,14 +71,14 @@ dependencies:
71
71
  requirements:
72
72
  - - "~>"
73
73
  - !ruby/object:Gem::Version
74
- version: 0.1.4
74
+ version: 0.1.5
75
75
  type: :runtime
76
76
  prerelease: false
77
77
  version_requirements: !ruby/object:Gem::Requirement
78
78
  requirements:
79
79
  - - "~>"
80
80
  - !ruby/object:Gem::Version
81
- version: 0.1.4
81
+ version: 0.1.5
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: bundler-audit
84
84
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +177,7 @@ files:
177
177
  - CHANGELOG.md
178
178
  - LICENSE.txt
179
179
  - README.md
180
+ - bin/check_tans_parser.sh
180
181
  - bin/tui-td
181
182
  - lib/tui_td.rb
182
183
  - lib/tui_td/ansi_parser.rb