syrma 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +4 -2
- data/lib/syrma/cli.rb +1 -1
- data/lib/syrma/session.rb +2 -2
- data/lib/syrma/snapshot.rb +23 -19
- data/lib/syrma/text_systems.rb +1 -1
- data/lib/syrma/version.rb +1 -1
- data/lib/syrma/window_driver.rb +6 -2
- data/lib/syrma.rb +0 -1
- data/sig/syrma.rbs +2 -0
- metadata +6 -7
- data/lib/syrma/internals.rb +0 -32
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be544217dc4ba67f34fa4cb6a824be44ab74dee130324f615e0824c5ab23fd72
|
|
4
|
+
data.tar.gz: 970b020a33399800791693b1922aacdfa679d4260947c75ff938838b31ad8794
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a2e0b783bc7e6c66fba35676fc1d10117e4c0436362eb494f32d0d5e857ff2a1ad50f2f743722fd13ae31633a6a5ba9fe9a0475d79cbf1796edf3f94a9a5f878
|
|
7
|
+
data.tar.gz: f9d84eba21b734e63dc793461b7f3b45fb75be5ffc862915ede839c245e269296b4dd8690c570a3cd29f9236aa02263d31dfc4b06ce278e5934f01e6c309ab9b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 - 2026-09-24
|
|
4
|
+
|
|
5
|
+
- Support Zaniah 0.7 inspection snapshots and semantic accessibility queries.
|
|
6
|
+
- Use Zaniah's public font, executor, and frame APIs instead of internal instrumentation.
|
|
7
|
+
|
|
3
8
|
## 0.3.0 - 2026-09-20
|
|
4
9
|
|
|
5
10
|
- Add deterministic virtual-clock demo recording with humanized input, pixel-space chrome, and Wezen output hooks.
|
data/README.md
CHANGED
|
@@ -54,7 +54,7 @@ bundle install
|
|
|
54
54
|
bundle exec syrma doctor
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
Syrma requires Ruby 3.1 or later and supports Zaniah `>= 0.
|
|
57
|
+
Syrma requires Ruby 3.1 or later and supports Zaniah `>= 0.7, < 1.0`. RSpec users can replace Minitest with RSpec in the test group.
|
|
58
58
|
|
|
59
59
|
### Demo recording
|
|
60
60
|
|
|
@@ -135,11 +135,13 @@ end
|
|
|
135
135
|
| Keyboard/text | `press`, `type`, `paste`, `compose`, `commit` |
|
|
136
136
|
| Window/input | `resize`, `close`, `drop_files`, `feed_terminal` |
|
|
137
137
|
| Synchronize | `settle`, `wait_for`, `advance` |
|
|
138
|
-
| Inspect | `tree`, `texts`, `at`, `pixel`, `screenshot`, `terminal_lines`, `menu`, `tooltip` |
|
|
138
|
+
| Inspect | `tree`, `texts`, `at`, `accessibility`, `pixel`, `screenshot`, `terminal_lines`, `menu`, `tooltip` |
|
|
139
139
|
| Assert | Text, element, visibility, panels, decorations, background, pixel, tooltip, menu, tree/terminal/image snapshots |
|
|
140
140
|
|
|
141
141
|
Locators are lazy: every operation resolves them against the latest rendered frame. Actions wait for visibility and an unobscured matching event handler, then send events through `Window#input`.
|
|
142
142
|
|
|
143
|
+
`ui.accessibility(role: :button, label: "Save")` searches Zaniah's semantic tree and returns `[node, path]` pairs. Unlike visual locators, semantic nodes need not correspond to a rendered element or have bounds; use `ui.find` for pointer actions.
|
|
144
|
+
|
|
143
145
|
Panel and decoration assertions use rendered `test_id` instrumentation, so application code does not need a Syrma or Canopus runtime dependency:
|
|
144
146
|
|
|
145
147
|
```ruby
|
data/lib/syrma/cli.rb
CHANGED
|
@@ -40,7 +40,7 @@ module Syrma
|
|
|
40
40
|
checks = {
|
|
41
41
|
"Ruby" => "#{RUBY_VERSION} (#{RUBY_PLATFORM})",
|
|
42
42
|
"zaniah" => Zaniah::VERSION,
|
|
43
|
-
"Bundled font" => File.file?(
|
|
43
|
+
"Bundled font" => File.file?(Zaniah.bundled_font_path) ? Zaniah.bundled_font_path : nil,
|
|
44
44
|
"YJIT" => defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled? ? "enabled" : "disabled",
|
|
45
45
|
"External encoding" => Encoding.default_external.name
|
|
46
46
|
}
|
data/lib/syrma/session.rb
CHANGED
|
@@ -50,7 +50,7 @@ module Syrma
|
|
|
50
50
|
@outputs = {}
|
|
51
51
|
@windows = attached_windows || [open_window(backend, width, height, window_options, keymap)]
|
|
52
52
|
@drivers = @windows.map { |window| prepare(window, backend, text, fonts) }
|
|
53
|
-
@font_paths = text == :deterministic ? [
|
|
53
|
+
@font_paths = text == :deterministic ? [Zaniah.bundled_font_path, *fonts.map { |font| File.expand_path(font) }] : []
|
|
54
54
|
@current = 0
|
|
55
55
|
mount&.call(window)
|
|
56
56
|
settle
|
|
@@ -102,7 +102,7 @@ module Syrma
|
|
|
102
102
|
candidate.tick
|
|
103
103
|
busy = true
|
|
104
104
|
end
|
|
105
|
-
return self unless busy || (@app &&
|
|
105
|
+
return self unless busy || (@app && !@app.executor.idle?)
|
|
106
106
|
end
|
|
107
107
|
raise UnstableUI, "UI did not stabilize within #{@max_frames} frames (continuous animation?)"
|
|
108
108
|
end
|
data/lib/syrma/snapshot.rb
CHANGED
|
@@ -14,10 +14,11 @@ module Syrma
|
|
|
14
14
|
def inspect = "#<#{type}#{test_id && " @#{test_id}"}#{text && " #{text.inspect}"} #{path.join('.')}>"
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
Tree = Data.define(:frame, :size, :root, :nodes, :hits, :text_runs, :menu, :menu_index, :tooltip) do
|
|
17
|
+
Tree = Data.define(:frame, :size, :root, :nodes, :hits, :text_runs, :menu, :menu_index, :tooltip, :hit_regions) do
|
|
18
18
|
def hit_at(point, event: :mouse_down, button: :left)
|
|
19
|
-
hits.reverse_each do |
|
|
20
|
-
|
|
19
|
+
hits.length.times.reverse_each do |index|
|
|
20
|
+
_bounds, node = hits[index]
|
|
21
|
+
next unless hit_regions[index].contains?(point)
|
|
21
22
|
|
|
22
23
|
return node if node.nil? || Tree.handles?(node, event, button)
|
|
23
24
|
end
|
|
@@ -27,7 +28,7 @@ module Syrma
|
|
|
27
28
|
def self.handles?(node, event, button)
|
|
28
29
|
case event
|
|
29
30
|
when :mouse_down
|
|
30
|
-
(button == :right &&
|
|
31
|
+
(button == :right && node.element.respond_to?(:context_menu_items) && node.element.context_menu_items) ||
|
|
31
32
|
(node.handlers & %i[mouse_down click drag]).any?
|
|
32
33
|
when :mouse_move then node.handlers.include?(:hover) || node.tooltip
|
|
33
34
|
when :mouse_up then node.handlers.include?(:mouse_up)
|
|
@@ -38,40 +39,43 @@ module Syrma
|
|
|
38
39
|
|
|
39
40
|
class SnapshotBuilder
|
|
40
41
|
def build(window)
|
|
42
|
+
snapshot = Zaniah::Inspection.snapshot(window)
|
|
41
43
|
by_element = {}.compare_by_identity
|
|
42
44
|
nodes = []
|
|
43
45
|
viewport = Zaniah::Bounds.new(0, 0, window.content_size.width, window.content_size.height)
|
|
44
|
-
root =
|
|
45
|
-
hits =
|
|
46
|
-
popup =
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
root = snapshot.root && visit(snapshot.root, viewport, [0], nodes, by_element)
|
|
47
|
+
hits = snapshot.hits.map { |hit| [hit.bounds, hit.owner && by_element[hit.owner]] }
|
|
48
|
+
popup = snapshot.overlays.popup
|
|
49
|
+
tooltip = snapshot.overlays.tooltip
|
|
50
|
+
Tree.new(frame: snapshot.frame.number, size: window.content_size, root: root,
|
|
51
|
+
nodes: nodes.freeze, hits: hits.freeze, hit_regions: snapshot.hits, text_runs: snapshot.text_runs,
|
|
49
52
|
menu: popup&.labels, menu_index: popup&.selected_index,
|
|
50
|
-
tooltip:
|
|
53
|
+
tooltip: tooltip && tooltip[:shown] ? tooltip[:text] : nil)
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
private
|
|
54
57
|
|
|
55
|
-
def visit(
|
|
56
|
-
bounds =
|
|
57
|
-
style =
|
|
58
|
+
def visit(entry, clip, path, nodes, by_element)
|
|
59
|
+
bounds = entry.bounds or return nil
|
|
60
|
+
style = entry.style
|
|
58
61
|
return nil if style[:display] == :none
|
|
59
62
|
|
|
60
63
|
visible = bounds.intersect(clip)
|
|
61
64
|
child_clip = style[:overflow] == :visible ? clip : visible
|
|
62
65
|
index = nodes.length
|
|
63
66
|
nodes << nil
|
|
64
|
-
children =
|
|
67
|
+
children = entry.children.each_with_index.filter_map do |child, child_index|
|
|
65
68
|
visit(child, child_clip, path + [child_index], nodes, by_element)
|
|
66
69
|
end
|
|
70
|
+
element = entry.element
|
|
67
71
|
node = Node.new(
|
|
68
|
-
path: path.freeze, element: element, type:
|
|
69
|
-
test_id:
|
|
72
|
+
path: path.freeze, element: element, type: entry.type.split("::").last.downcase.to_sym,
|
|
73
|
+
test_id: entry.test_id, key: entry.key,
|
|
70
74
|
text: text_attr(element, :text), color: text_attr(element, :text_color),
|
|
71
75
|
font_size: text_attr(element, :font_size), bounds: bounds, visible_bounds: visible,
|
|
72
|
-
background:
|
|
73
|
-
radius:
|
|
74
|
-
tooltip:
|
|
76
|
+
background: style[:background], border_color: style[:border_color],
|
|
77
|
+
radius: style[:corner_radii], handlers: entry.handlers,
|
|
78
|
+
tooltip: entry.tooltip, children: children.freeze
|
|
75
79
|
)
|
|
76
80
|
nodes[index] = node
|
|
77
81
|
by_element[element] = node
|
data/lib/syrma/text_systems.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Syrma
|
|
|
9
9
|
when :none, nil then nil
|
|
10
10
|
when :native then Zaniah::TextSystem::Renderer.new
|
|
11
11
|
when :deterministic
|
|
12
|
-
paths = [
|
|
12
|
+
paths = [Zaniah.bundled_font_path, *fonts.map { |font| File.expand_path(font) }]
|
|
13
13
|
missing = paths.reject { |path| File.file?(path) }
|
|
14
14
|
raise ArgumentError, "Fonts not found: #{missing.join(', ')}" unless missing.empty?
|
|
15
15
|
|
data/lib/syrma/version.rb
CHANGED
data/lib/syrma/window_driver.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Syrma
|
|
|
17
17
|
|
|
18
18
|
def tree
|
|
19
19
|
session.settle
|
|
20
|
-
@tree = nil if @tree && @tree.frame != window.
|
|
20
|
+
@tree = nil if @tree && @tree.frame != window.frame_number
|
|
21
21
|
@tree ||= @builder.build(window)
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -27,6 +27,10 @@ module Syrma
|
|
|
27
27
|
def text(value) = find(text: value)
|
|
28
28
|
def texts = tree.text_runs.map { |run| run[2] }
|
|
29
29
|
def tooltip = tree.tooltip
|
|
30
|
+
def accessibility(role: nil, label: nil, states: {})
|
|
31
|
+
session.settle
|
|
32
|
+
Zaniah::Inspection.snapshot(window).accessibility.query(role: role, label: label, states: states)
|
|
33
|
+
end
|
|
30
34
|
|
|
31
35
|
def button(label)
|
|
32
36
|
find(clickable: true) do |node|
|
|
@@ -182,7 +186,7 @@ module Syrma
|
|
|
182
186
|
def menu = (@menu_driver ||= PopupDriver.new(self))
|
|
183
187
|
|
|
184
188
|
def dispatch(event)
|
|
185
|
-
session.event_log.add(window.
|
|
189
|
+
session.event_log.add(window.frame_number, window.title, event)
|
|
186
190
|
window.input(event)
|
|
187
191
|
session.settle unless @in_gesture && session.event_frames == :gesture
|
|
188
192
|
event
|
data/lib/syrma.rb
CHANGED
|
@@ -4,7 +4,6 @@ require "zaniah"
|
|
|
4
4
|
require_relative "syrma/version"
|
|
5
5
|
require_relative "syrma/configuration"
|
|
6
6
|
require_relative "syrma/errors"
|
|
7
|
-
require_relative "syrma/internals"
|
|
8
7
|
require_relative "syrma/instrumentation"
|
|
9
8
|
require_relative "syrma/clock"
|
|
10
9
|
require_relative "syrma/text_systems"
|
data/sig/syrma.rbs
CHANGED
|
@@ -78,6 +78,7 @@ module Syrma
|
|
|
78
78
|
attr_reader root: Node?
|
|
79
79
|
attr_reader nodes: Array[Node]
|
|
80
80
|
attr_reader hits: Array[untyped]
|
|
81
|
+
attr_reader hit_regions: Array[untyped]
|
|
81
82
|
attr_reader text_runs: Array[untyped]
|
|
82
83
|
attr_reader menu: Array[String]?
|
|
83
84
|
attr_reader menu_index: Integer?
|
|
@@ -200,6 +201,7 @@ module Syrma
|
|
|
200
201
|
def text: (String | Regexp value) -> Locator
|
|
201
202
|
def texts: () -> Array[String]
|
|
202
203
|
def tooltip: () -> String?
|
|
204
|
+
def accessibility: (?role: Symbol?, ?label: String | Regexp | nil, ?states: Hash[Symbol, untyped]) -> Array[[untyped, Array[Integer]]]
|
|
203
205
|
def button: (String label) -> Locator
|
|
204
206
|
def at: (untyped x, ?untyped y, ?event: Symbol, ?button: Symbol) -> Node?
|
|
205
207
|
def pixel: (Integer x, Integer y) -> Array[Integer]
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: syrma
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yudai Takada
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: zaniah
|
|
@@ -16,20 +16,20 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0.
|
|
19
|
+
version: '0.7'
|
|
20
20
|
- - "<"
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '0
|
|
22
|
+
version: '1.0'
|
|
23
23
|
type: :runtime
|
|
24
24
|
prerelease: false
|
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
26
|
requirements:
|
|
27
27
|
- - ">="
|
|
28
28
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '0.
|
|
29
|
+
version: '0.7'
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0
|
|
32
|
+
version: '1.0'
|
|
33
33
|
- !ruby/object:Gem::Dependency
|
|
34
34
|
name: wezen
|
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -73,7 +73,6 @@ files:
|
|
|
73
73
|
- lib/syrma/event_codec.rb
|
|
74
74
|
- lib/syrma/event_log.rb
|
|
75
75
|
- lib/syrma/instrumentation.rb
|
|
76
|
-
- lib/syrma/internals.rb
|
|
77
76
|
- lib/syrma/minitest.rb
|
|
78
77
|
- lib/syrma/popup_driver.rb
|
|
79
78
|
- lib/syrma/query.rb
|
data/lib/syrma/internals.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Syrma
|
|
4
|
-
module Internals
|
|
5
|
-
module_function
|
|
6
|
-
|
|
7
|
-
def style(element) = element.instance_variable_get(:@style) || {}
|
|
8
|
-
def background(element) = paint_value(element, :background, :@background)
|
|
9
|
-
def border_color(element) = paint_value(element, :border_color, :@border_color)
|
|
10
|
-
def radius(element) = paint_value(element, :corner_radii, :@radius)
|
|
11
|
-
def tooltip(element) = element.instance_variable_get(:@tooltip)
|
|
12
|
-
def context_menu(element) = element.instance_variable_get(:@context_menu)
|
|
13
|
-
def key(element) = element.instance_variable_get(:@key)
|
|
14
|
-
def shown_tooltip(window)
|
|
15
|
-
tip = window.instance_variable_get(:@tooltip)
|
|
16
|
-
tip && tip[:shown] ? tip[:text] : nil
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def bundled_font_path
|
|
20
|
-
_, entry = $LOAD_PATH.resolve_feature_path("zaniah")
|
|
21
|
-
File.expand_path("../assets/fonts/Abel-Regular.ttf", File.dirname(entry))
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def executor_idle?(executor) = executor.instance_variable_get(:@foreground).empty?
|
|
25
|
-
|
|
26
|
-
def paint_value(element, property, legacy)
|
|
27
|
-
resolved = element.respond_to?(:resolved_style) ? element.resolved_style : nil
|
|
28
|
-
resolved&.[](property) || element.instance_variable_get(legacy) || style(element)[property]
|
|
29
|
-
end
|
|
30
|
-
private_class_method :paint_value
|
|
31
|
-
end
|
|
32
|
-
end
|