@specsage/cli 0.1.11 → 0.1.13
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/lib/browser.js +3 -0
- package/lib/client_errors.rb +6 -0
- package/lib/dialogs.js +3 -0
- package/lib/runner.rb +23 -11
- package/lib/step_client.rb +11 -1
- package/package.json +1 -1
package/lib/browser.js
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared base error type for step clients (StepClient, DirectStepClient).
|
|
4
|
+
# Runner can rescue this base type to handle errors from either client.
|
|
5
|
+
# Each client defines its own StepError subclass for API stability.
|
|
6
|
+
class StepClientError < StandardError; end
|
package/lib/dialogs.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Dialog handling module for SpecSage browser automation.
|
|
3
3
|
* Captures JavaScript dialogs (alert, confirm, prompt) and exposes them
|
|
4
4
|
* to the AI agent via visual overlays and pseudo-elements.
|
|
5
|
+
*
|
|
6
|
+
* DO NOT EDIT packages/cli/lib/dialogs.js - it is copied from this file during npm publish.
|
|
7
|
+
* See bin/publish_npm_package for details.
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
// Pending dialog state
|
package/lib/runner.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
# DO NOT EDIT packages/cli/lib/runner.rb - it is copied from this file during npm publish.
|
|
5
|
+
# See bin/publish_npm_package for details.
|
|
6
|
+
|
|
4
7
|
require 'json'
|
|
5
8
|
require 'timeout'
|
|
6
9
|
require 'fileutils'
|
|
@@ -8,6 +11,7 @@ require 'fileutils'
|
|
|
8
11
|
# Determine SpecSage home directory for locating resources
|
|
9
12
|
SPECSAGE_HOME ||= File.expand_path('..', __dir__)
|
|
10
13
|
|
|
14
|
+
require_relative 'client_errors'
|
|
11
15
|
require_relative 'step_client'
|
|
12
16
|
|
|
13
17
|
class Runner
|
|
@@ -22,7 +26,8 @@ class Runner
|
|
|
22
26
|
|
|
23
27
|
# Initialize runner with scenario data from server
|
|
24
28
|
# @param all_scenarios [Hash] optional map of scenario_id => scenario_data for pre-scenario lookup
|
|
25
|
-
|
|
29
|
+
# @param step_client [StepClient, DirectStepClient] optional pre-configured client for step processing
|
|
30
|
+
def initialize(scenario_data, visible: false, record: false, publisher: nil, server_run_id: nil, all_scenarios: nil, step_client: nil)
|
|
26
31
|
@scenario = normalize_scenario_data(scenario_data)
|
|
27
32
|
@scenario_id = @scenario['id']
|
|
28
33
|
@scenario_name = @scenario['name'] || @scenario['id'] || 'unnamed'
|
|
@@ -35,7 +40,7 @@ class Runner
|
|
|
35
40
|
@next_request_id = 1
|
|
36
41
|
@node_channel_poisoned = false
|
|
37
42
|
@publisher = publisher
|
|
38
|
-
@step_client = nil
|
|
43
|
+
@step_client = step_client # Accept pre-configured client (nil = create StepClient in run())
|
|
39
44
|
@server_run_id = server_run_id
|
|
40
45
|
@credentials = {} # Credentials received from server { "NAME" => "value" }
|
|
41
46
|
@max_steps = nil # Max browser actions allowed, received from server on first step
|
|
@@ -48,7 +53,10 @@ class Runner
|
|
|
48
53
|
|
|
49
54
|
raise ArgumentError, 'server_run_id is required' unless @server_run_id
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
# Only create StepClient if not injected (CLI path uses HTTP, Sidekiq injects DirectStepClient)
|
|
57
|
+
# TODO: Validate publisher presence here when step_client is nil. Currently crashes with
|
|
58
|
+
# NoMethodError on @publisher.base_url if caller omits both step_client and publisher.
|
|
59
|
+
@step_client ||= StepClient.new(
|
|
52
60
|
base_url: @publisher.base_url,
|
|
53
61
|
server_run_id: @server_run_id,
|
|
54
62
|
api_key: @publisher.api_key
|
|
@@ -69,7 +77,7 @@ class Runner
|
|
|
69
77
|
stop_node_process
|
|
70
78
|
upload_video
|
|
71
79
|
cleanup_temp_dir
|
|
72
|
-
return
|
|
80
|
+
return 'ERROR'
|
|
73
81
|
end
|
|
74
82
|
|
|
75
83
|
pre_scenario_name = pre_scenario_data['name'] || pre_scenario_id
|
|
@@ -92,14 +100,14 @@ class Runner
|
|
|
92
100
|
stop_node_process
|
|
93
101
|
upload_video
|
|
94
102
|
cleanup_temp_dir
|
|
95
|
-
return
|
|
103
|
+
return 'FAIL'
|
|
96
104
|
elsif result[:verdict] == 'ERROR'
|
|
97
105
|
log "Pre-scenario '#{pre_scenario_name}' ERROR - skipping main scenario"
|
|
98
106
|
send_main_scenario_verdict('PRE_SCENARIO_ERROR', "Pre-scenario '#{pre_scenario_name}' errored: #{result[:reason]}")
|
|
99
107
|
stop_node_process
|
|
100
108
|
upload_video
|
|
101
109
|
cleanup_temp_dir
|
|
102
|
-
return
|
|
110
|
+
return 'ERROR'
|
|
103
111
|
end
|
|
104
112
|
|
|
105
113
|
log "Pre-scenario '#{pre_scenario_name}' PASSED"
|
|
@@ -109,7 +117,7 @@ class Runner
|
|
|
109
117
|
end
|
|
110
118
|
|
|
111
119
|
# Run the main scenario
|
|
112
|
-
run_single_scenario(
|
|
120
|
+
result = run_single_scenario(
|
|
113
121
|
scenario_id: @scenario_id,
|
|
114
122
|
scenario_data: @scenario,
|
|
115
123
|
pre_scenario_for_id: nil,
|
|
@@ -119,16 +127,20 @@ class Runner
|
|
|
119
127
|
stop_node_process
|
|
120
128
|
upload_video
|
|
121
129
|
cleanup_temp_dir
|
|
122
|
-
|
|
130
|
+
|
|
131
|
+
result[:verdict]
|
|
132
|
+
rescue StepClientError => e
|
|
123
133
|
send_client_verdict_if_needed('ERROR', "Server error: #{e.message}")
|
|
124
134
|
stop_node_process
|
|
125
135
|
upload_video
|
|
126
136
|
cleanup_temp_dir
|
|
137
|
+
'ERROR'
|
|
127
138
|
rescue StandardError => e
|
|
128
139
|
send_client_verdict_if_needed('ERROR', e.message)
|
|
129
140
|
stop_node_process
|
|
130
141
|
upload_video
|
|
131
142
|
cleanup_temp_dir
|
|
143
|
+
'ERROR'
|
|
132
144
|
end
|
|
133
145
|
|
|
134
146
|
# Run a single scenario (either pre-scenario or main scenario)
|
|
@@ -238,7 +250,7 @@ class Runner
|
|
|
238
250
|
status: status,
|
|
239
251
|
reason: reason
|
|
240
252
|
)
|
|
241
|
-
rescue
|
|
253
|
+
rescue StepClientError => e
|
|
242
254
|
log "Warning: Failed to send main scenario verdict: #{e.message}"
|
|
243
255
|
end
|
|
244
256
|
|
|
@@ -580,7 +592,7 @@ class Runner
|
|
|
580
592
|
log "Uploading video (#{@video_data.bytesize} bytes)..."
|
|
581
593
|
@step_client.upload_video(scenario_id: @scenario_id, video_data: @video_data)
|
|
582
594
|
log "Video uploaded successfully."
|
|
583
|
-
rescue
|
|
595
|
+
rescue StepClientError => e
|
|
584
596
|
log "Warning: Failed to upload video: #{e.message}"
|
|
585
597
|
end
|
|
586
598
|
|
|
@@ -634,7 +646,7 @@ class Runner
|
|
|
634
646
|
status: status,
|
|
635
647
|
reason: reason
|
|
636
648
|
)
|
|
637
|
-
rescue
|
|
649
|
+
rescue StepClientError => e
|
|
638
650
|
log "Warning: Failed to send verdict to server: #{e.message}"
|
|
639
651
|
end
|
|
640
652
|
|
package/lib/step_client.rb
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# DO NOT EDIT packages/cli/lib/step_client.rb - it is copied from this file during npm publish.
|
|
4
|
+
# See bin/publish_npm_package for details.
|
|
5
|
+
|
|
3
6
|
require "net/http"
|
|
4
7
|
require "uri"
|
|
5
8
|
require "json"
|
|
6
9
|
require "base64"
|
|
10
|
+
require_relative "client_errors"
|
|
7
11
|
|
|
8
12
|
class StepClient
|
|
9
|
-
|
|
13
|
+
# Subclass shared error for API stability (supports subclassing, .class checks, .name)
|
|
14
|
+
# Runner can rescue either StepClient::StepError or the base StepClientError
|
|
15
|
+
class StepError < StepClientError; end
|
|
10
16
|
|
|
11
17
|
attr_reader :server_run_id
|
|
12
18
|
|
|
@@ -56,6 +62,10 @@ class StepClient
|
|
|
56
62
|
|
|
57
63
|
response = post("/api/runs/#{@server_run_id}/step", body)
|
|
58
64
|
|
|
65
|
+
# TODO: Credentials defaulted to {} may change semantics. Server returns nil when no
|
|
66
|
+
# credentials are present (after first step). Returning {} instead of nil may cause
|
|
67
|
+
# downstream code to branch incorrectly (`if credentials` is always truthy). Consider
|
|
68
|
+
# returning nil when server returns nil, or document this as intentional contract.
|
|
59
69
|
{
|
|
60
70
|
action: response["action"],
|
|
61
71
|
step_number: response["step_number"],
|