ruby_raider 4.1.0 → 4.2.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/.github/workflows/e2e_tests.yml +3 -0
- data/README.md +52 -53
- data/RELEASE.md +14 -1
- data/lib/commands/schedule_commands.rb +84 -0
- data/lib/generators/templates/common/git_ignore.tt +1 -0
- data/lib/ruby_raider/runtime/retry_reset.rb +32 -0
- data/lib/ruby_raider/runtime/rspec.rb +1 -0
- data/lib/ruby_raider/runtime/show_browser.rb +53 -0
- data/lib/ruby_raider/runtime.rb +2 -0
- data/lib/ruby_raider.rb +22 -7
- data/lib/utilities/schedules/adapter.rb +22 -0
- data/lib/utilities/schedules/base.rb +27 -0
- data/lib/utilities/schedules/kind.rb +74 -0
- data/lib/utilities/schedules/legacy.rb +196 -0
- data/lib/utilities/schedules/legacy_stub.rb +62 -0
- data/lib/utilities/{flows → schedules}/next_run.rb +2 -2
- data/lib/utilities/{flows → schedules}/notifier.rb +6 -6
- data/lib/utilities/{flows → schedules}/os/command_runner.rb +1 -1
- data/lib/utilities/{flows → schedules}/os/cron.rb +27 -17
- data/lib/utilities/{flows → schedules}/os/launchd.rb +32 -14
- data/lib/utilities/{flows → schedules}/os/linux.rb +23 -5
- data/lib/utilities/{flows → schedules}/os/systemd.rb +33 -17
- data/lib/utilities/{flows → schedules}/os/task_scheduler.rb +36 -20
- data/lib/utilities/schedules/os_enumerator.rb +30 -0
- data/lib/utilities/schedules/project.rb +70 -0
- data/lib/utilities/schedules/rule.rb +162 -0
- data/lib/utilities/schedules/run_lock.rb +160 -0
- data/lib/utilities/schedules/run_log.rb +122 -0
- data/lib/utilities/schedules/run_report.rb +139 -0
- data/lib/utilities/schedules/run_wrapper.rb +216 -0
- data/lib/utilities/schedules/schedule.rb +89 -0
- data/lib/utilities/schedules/schedule_store.rb +53 -0
- data/lib/utilities/schedules/scheduler.rb +178 -0
- data/lib/utilities/schedules/scheduler_steps.rb +69 -0
- data/lib/utilities/schedules/selection.rb +133 -0
- data/lib/utilities/schedules/spawn.rb +68 -0
- data/lib/utilities/{flows/schedule_store.rb → schedules/store.rb} +17 -13
- data/lib/version +1 -1
- data/ruby_raider.gemspec +1 -0
- data/sig/commands/schedule_commands.rbs +12 -0
- data/sig/ruby_raider/runtime/retry_reset.rbs +7 -0
- data/sig/ruby_raider/runtime/show_browser.rbs +27 -0
- data/sig/ruby_raider.rbs +4 -0
- data/sig/utilities/schedules/adapter.rbs +5 -0
- data/sig/utilities/schedules/base.rbs +15 -0
- data/sig/utilities/schedules/kind.rbs +32 -0
- data/sig/utilities/schedules/legacy.rbs +45 -0
- data/sig/utilities/schedules/legacy_stub.rbs +15 -0
- data/sig/utilities/{flows → schedules}/next_run.rbs +3 -3
- data/sig/utilities/{flows → schedules}/notifier.rbs +1 -1
- data/sig/utilities/{flows → schedules}/os/command_runner.rbs +1 -1
- data/sig/utilities/{flows → schedules}/os/cron.rbs +7 -7
- data/sig/utilities/{flows → schedules}/os/launchd.rbs +7 -6
- data/sig/utilities/{flows → schedules}/os/linux.rbs +10 -4
- data/sig/utilities/{flows → schedules}/os/systemd.rbs +8 -7
- data/sig/utilities/{flows → schedules}/os/task_scheduler.rbs +13 -10
- data/sig/utilities/schedules/os_enumerator.rbs +12 -0
- data/sig/utilities/schedules/project.rbs +22 -0
- data/sig/utilities/{flows/schedule.rbs → schedules/rule.rbs} +23 -12
- data/sig/utilities/schedules/run_lock.rbs +29 -0
- data/sig/utilities/schedules/run_log.rbs +33 -0
- data/sig/utilities/schedules/run_report.rbs +34 -0
- data/sig/utilities/schedules/run_wrapper.rbs +45 -0
- data/sig/utilities/schedules/schedule.rbs +27 -0
- data/sig/utilities/schedules/schedule_store.rbs +21 -0
- data/sig/utilities/{flows → schedules}/scheduler.rbs +22 -15
- data/sig/utilities/schedules/scheduler_steps.rbs +22 -0
- data/sig/utilities/schedules/selection.rbs +36 -0
- data/sig/utilities/schedules/spawn.rbs +10 -0
- data/sig/utilities/{flows/schedule_store.rbs → schedules/store.rbs} +14 -9
- data/spec/commands/schedule_commands_spec.rb +284 -0
- data/spec/integration/content/common_content_spec.rb +8 -2
- data/spec/integration/content/gemfile_content_spec.rb +2 -2
- data/spec/integration/end_to_end_spec.rb +0 -63
- data/spec/runtime/retry_reset_spec.rb +112 -0
- data/spec/runtime/show_browser_project_spec.rb +70 -0
- data/spec/runtime/show_browser_spec.rb +101 -0
- data/spec/utilities/schedules/kind_spec.rb +81 -0
- data/spec/utilities/schedules/legacy_spec.rb +433 -0
- data/spec/utilities/schedules/legacy_stub_spec.rb +144 -0
- data/spec/utilities/{flows → schedules}/next_run_spec.rb +5 -5
- data/spec/utilities/{flows → schedules}/notifier_spec.rb +3 -4
- data/spec/utilities/{flows → schedules}/os/command_runner_spec.rb +2 -3
- data/spec/utilities/{flows → schedules}/os/cron_spec.rb +34 -29
- data/spec/utilities/{flows → schedules}/os/launchd_spec.rb +42 -29
- data/spec/utilities/{flows → schedules}/os/linux_spec.rb +27 -13
- data/spec/utilities/schedules/os/list_ids_spec.rb +202 -0
- data/spec/utilities/{flows → schedules}/os/systemd_spec.rb +35 -31
- data/spec/utilities/{flows → schedules}/os/task_scheduler_spec.rb +40 -34
- data/spec/utilities/schedules/os_entry_spec.rb +54 -0
- data/spec/utilities/schedules/os_enumerator_spec.rb +57 -0
- data/spec/utilities/schedules/project_spec.rb +125 -0
- data/spec/utilities/schedules/run_lock_spec.rb +143 -0
- data/spec/utilities/schedules/run_log_spec.rb +83 -0
- data/spec/utilities/schedules/run_report_spec.rb +150 -0
- data/spec/utilities/schedules/run_wrapper_integration_spec.rb +166 -0
- data/spec/utilities/schedules/run_wrapper_spec.rb +430 -0
- data/spec/utilities/schedules/schedule_spec.rb +120 -0
- data/spec/utilities/schedules/schedule_store_spec.rb +151 -0
- data/spec/utilities/schedules/scheduler_launchd_spec.rb +77 -0
- data/spec/utilities/schedules/scheduler_spec.rb +445 -0
- data/spec/utilities/schedules/selection_spec.rb +176 -0
- data/spec/utilities/schedules/spawn_spec.rb +104 -0
- metadata +106 -53
- data/lib/commands/flow_commands.rb +0 -104
- data/lib/generators/templates/flows/flow_helper.tt +0 -42
- data/lib/generators/templates/flows/run.tt +0 -332
- data/lib/utilities/flows/project.rb +0 -170
- data/lib/utilities/flows/run_log.rb +0 -61
- data/lib/utilities/flows/runner.rb +0 -206
- data/lib/utilities/flows/schedule.rb +0 -221
- data/lib/utilities/flows/scheduler.rb +0 -187
- data/sig/commands/flow_commands.rbs +0 -13
- data/sig/utilities/flows/project.rbs +0 -46
- data/sig/utilities/flows/run_log.rbs +0 -22
- data/sig/utilities/flows/runner.rbs +0 -35
- data/spec/commands/flow_commands_spec.rb +0 -128
- data/spec/generators/flow_helper_spec.rb +0 -40
- data/spec/integration/flows/flow_project.rb +0 -62
- data/spec/integration/flows/run_command_spec.rb +0 -91
- data/spec/integration/flows/run_lifecycle_spec.rb +0 -383
- data/spec/utilities/flows/project_spec.rb +0 -178
- data/spec/utilities/flows/run_log_spec.rb +0 -47
- data/spec/utilities/flows/runner_spec.rb +0 -313
- data/spec/utilities/flows/schedule_spec.rb +0 -233
- data/spec/utilities/flows/schedule_store_spec.rb +0 -137
- data/spec/utilities/flows/scheduler_spec.rb +0 -329
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Schedules
|
|
3
|
+
class RunWrapper
|
|
4
|
+
EXIT_CODES: Hash[String, Integer]
|
|
5
|
+
TRIGGERS: Array[String]
|
|
6
|
+
LOG_BYTES: Integer
|
|
7
|
+
MISSING: String
|
|
8
|
+
ZERO: Hash[String, Integer]
|
|
9
|
+
SPAWNER: ^(Hash[String, String?], Array[String], String) -> [Integer?, bool]
|
|
10
|
+
|
|
11
|
+
@project: Project
|
|
12
|
+
@out: IO | StringIO
|
|
13
|
+
@err: IO | StringIO
|
|
14
|
+
@spawner: untyped
|
|
15
|
+
@clock: ^() -> Time
|
|
16
|
+
@notifier: untyped
|
|
17
|
+
@lock: RunLock
|
|
18
|
+
|
|
19
|
+
def initialize: (Project project, ?out: IO | StringIO, ?err: IO | StringIO, ?spawner: untyped, ?clock: ^() -> Time, ?notifier: untyped) -> void
|
|
20
|
+
def run: (String name, ?trigger: String) -> Integer
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def load: (String name) -> Schedule
|
|
25
|
+
def attempt: (Schedule schedule, Hash[String, String] record) -> Hash[Symbol, untyped]
|
|
26
|
+
def stopped: (Schedule schedule, Hash[String, String] record, Hash[Symbol, untyped] outcome) -> Hash[Symbol, untyped]
|
|
27
|
+
def spawn: (Schedule schedule) -> Hash[Symbol, untyped]
|
|
28
|
+
def status: (Integer? code, bool stopped, Hash[String, Integer]? counts) -> String
|
|
29
|
+
def argv: (Schedule schedule) -> Array[String]
|
|
30
|
+
def env: (Schedule schedule) -> Hash[String, String?]
|
|
31
|
+
def gem_env: (Schedule schedule) -> Hash[String, String?]
|
|
32
|
+
def copy_report: (Schedule schedule, RunReport? report) -> String?
|
|
33
|
+
def append: (Schedule schedule, Hash[String, String] record, Hash[Symbol, untyped] outcome) -> void
|
|
34
|
+
def finish: (Schedule schedule, Hash[String, String] record, Hash[Symbol, untyped] outcome) -> Integer
|
|
35
|
+
def append_quietly: (Schedule schedule, Hash[String, String] record, Hash[Symbol, untyped] outcome) -> void
|
|
36
|
+
def result_text: (Schedule schedule, Hash[String, String] record, Hash[Symbol, untyped] outcome) -> String
|
|
37
|
+
def failures: (Hash[String, Integer] counts) -> String
|
|
38
|
+
def tests: (Integer total) -> String
|
|
39
|
+
def notify: (Schedule schedule, Hash[String, String] record, Hash[Symbol, untyped] outcome) -> bool
|
|
40
|
+
def start_reason: (Schedule schedule, Hash[String, String] record, Hash[Symbol, untyped] outcome, long: bool) -> String
|
|
41
|
+
def rotate_log: (Schedule schedule) -> void
|
|
42
|
+
def stamp: () -> String
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Schedules
|
|
3
|
+
class Schedule < Rule
|
|
4
|
+
KIND: Kind
|
|
5
|
+
KEYS: Array[String]
|
|
6
|
+
LINUX: Regexp
|
|
7
|
+
NO_BROWSER_ON_LINUX: String
|
|
8
|
+
|
|
9
|
+
attr_reader show_browser: bool
|
|
10
|
+
attr_reader selection: Selection
|
|
11
|
+
|
|
12
|
+
def self.kind: () -> Kind
|
|
13
|
+
def self.build: (project: Project, name: String, every: String, ?at: String?, ?on: String?, ?retries: untyped, ?notify: bool, ?show_browser: bool,
|
|
14
|
+
?selection: Selection, ?now: Time, ?host_os: String) -> Schedule
|
|
15
|
+
def self.from_h: (untyped hash, String project_root, ?host_os: String) -> Schedule
|
|
16
|
+
|
|
17
|
+
def initialize: (name: untyped, every: untyped, at: untyped, on: untyped, retries: untyped, selection: untyped, os_id: untyped, ruby: untyped, bundle: untyped,
|
|
18
|
+
gem_env: untyped, project_root: untyped, created_at: untyped, ?notify: untyped, ?show_browser: untyped) -> void
|
|
19
|
+
def to_h: () -> Hash[String, untyped]
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def checked_name: (untyped name) -> String
|
|
24
|
+
def checked_selection: (untyped selection) -> Selection
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Schedules
|
|
3
|
+
class ScheduleStore < Store
|
|
4
|
+
DIR: String
|
|
5
|
+
GITIGNORE_LINE: String
|
|
6
|
+
|
|
7
|
+
def initialize: (Project project) -> void
|
|
8
|
+
def all: () -> Array[Schedule]
|
|
9
|
+
def find: (String name) -> Schedule?
|
|
10
|
+
def save: (Schedule schedule) -> Schedule
|
|
11
|
+
def delete: (String name) -> (Schedule | Store::Removed)?
|
|
12
|
+
def ignore_in_git: () -> bool
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def gitignore: () -> String
|
|
17
|
+
def gitignore_text: (File file) -> String
|
|
18
|
+
def ignored?: (String content) -> bool
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
module RubyRaider
|
|
2
|
-
module
|
|
2
|
+
module Schedules
|
|
3
3
|
class Scheduler
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
LAST: Array[String]
|
|
4
|
+
include SchedulerSteps
|
|
5
|
+
|
|
6
|
+
KIND: Kind
|
|
8
7
|
TIME: String
|
|
8
|
+
LIST_TIME: String
|
|
9
|
+
LAST: Array[String]
|
|
10
|
+
EMPTY: String
|
|
11
|
+
UNLISTED: String
|
|
12
|
+
RULE_KEYS: Array[String]
|
|
9
13
|
|
|
10
14
|
type row = { json: Hash[String, untyped], text: String }
|
|
11
15
|
|
|
@@ -15,28 +19,31 @@ module RubyRaider
|
|
|
15
19
|
@out: IO | StringIO
|
|
16
20
|
@err: IO | StringIO
|
|
17
21
|
@now: ^() -> Time
|
|
22
|
+
@host_os: String
|
|
18
23
|
|
|
19
24
|
def self.adapter: (?runner: Os::CommandRunner, ?host_os: String) -> (Os::Launchd | Os::TaskScheduler | Os::Linux)
|
|
20
25
|
|
|
21
|
-
def initialize: (Project project, ?adapter: untyped, ?store: ScheduleStore, ?out: IO | StringIO, ?err: IO | StringIO, ?now: ^() -> Time
|
|
22
|
-
|
|
23
|
-
def
|
|
24
|
-
|
|
26
|
+
def initialize: (Project project, ?adapter: untyped, ?store: ScheduleStore, ?out: IO | StringIO, ?err: IO | StringIO, ?now: ^() -> Time,
|
|
27
|
+
?host_os: String) -> void
|
|
28
|
+
def add: (String name, every: String?, ?files: Array[String], ?tag: String?, ?at: String?, ?on: String?, ?retries: untyped, ?notify: bool,
|
|
29
|
+
?show_browser: bool) -> void
|
|
30
|
+
def remove: (String name) -> void
|
|
31
|
+
def list: (?json: bool, ?notice: String?) -> void
|
|
25
32
|
|
|
26
33
|
private
|
|
27
34
|
|
|
35
|
+
attr_reader store: ScheduleStore
|
|
36
|
+
|
|
28
37
|
def adapter: () -> untyped
|
|
29
|
-
def
|
|
30
|
-
def
|
|
31
|
-
def
|
|
32
|
-
def private_dir: (String relative) -> void
|
|
38
|
+
def checked_name: (untyped name) -> void
|
|
39
|
+
def schedule_dirs: (String name) -> void
|
|
40
|
+
def ignore_in_git: () -> void
|
|
33
41
|
def row: (Schedule schedule, Time now) -> row
|
|
34
42
|
def unlisted_rows: (Array[String] listed) -> Array[row]
|
|
35
|
-
def
|
|
43
|
+
def unlisted_names: () -> Array[String]
|
|
36
44
|
def os_state: (Schedule schedule) -> String
|
|
37
45
|
def rule: (Schedule schedule) -> String
|
|
38
46
|
def last_run: (String name) -> Hash[String, untyped]?
|
|
39
|
-
def line: (String name, String rule, String next_run, Hash[String, untyped]? last, String state) -> String
|
|
40
47
|
end
|
|
41
48
|
end
|
|
42
49
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Schedules
|
|
3
|
+
module SchedulerSteps
|
|
4
|
+
RUBY_NOT_FOUND: String
|
|
5
|
+
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
# Given by the includer
|
|
9
|
+
def adapter: () -> untyped
|
|
10
|
+
def store: () -> Store
|
|
11
|
+
|
|
12
|
+
def save: (untyped schedule) -> untyped
|
|
13
|
+
def delete: (String name, String label) -> untyped
|
|
14
|
+
def unreadable_warning: (String name, String label) -> String
|
|
15
|
+
def ruby_found?: (untyped schedule) -> bool
|
|
16
|
+
def os_entry_state: (String os_id) -> String
|
|
17
|
+
def present?: (String os_id) -> bool
|
|
18
|
+
def missed_runs_note: () -> String?
|
|
19
|
+
def line: (String name, String rule, String next_run, Hash[String, untyped]? last, String state) -> String
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Schedules
|
|
3
|
+
class Selection
|
|
4
|
+
KINDS: Array[String]
|
|
5
|
+
KEYS: Array[String]
|
|
6
|
+
TAG: Regexp
|
|
7
|
+
MAX_FILES: Integer
|
|
8
|
+
WINDOWS: Regexp
|
|
9
|
+
FILE_RULE: String
|
|
10
|
+
WINDOWS_RULE: String
|
|
11
|
+
|
|
12
|
+
attr_reader kind: String
|
|
13
|
+
attr_reader tag: String?
|
|
14
|
+
attr_reader files: Array[String]
|
|
15
|
+
|
|
16
|
+
def self.all: () -> Selection
|
|
17
|
+
def self.parse: (files: untyped, tag: untyped, root: String, ?host_os: String) -> Selection
|
|
18
|
+
def self.from_h: (untyped hash, root: String, ?host_os: String) -> Selection
|
|
19
|
+
|
|
20
|
+
def initialize: (kind: untyped, ?tag: untyped, ?files: untyped, ?host_os: String) -> void
|
|
21
|
+
def missing_file: (String root) -> String?
|
|
22
|
+
def check!: (String root) -> self
|
|
23
|
+
def to_h: () -> Hash[String, untyped]
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def nothing: (String label, untyped value) -> nil
|
|
28
|
+
def checked_tag: (untyped tag) -> String
|
|
29
|
+
def checked_files: (untyped files, bool windows) -> Array[String]
|
|
30
|
+
def checked_file: (untyped file, bool windows) -> String
|
|
31
|
+
def spec_path?: (untyped file, bool windows) -> bool
|
|
32
|
+
def relative?: (String file, bool windows) -> bool
|
|
33
|
+
def present?: (String root, String file) -> bool
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module RubyRaider
|
|
2
|
+
module Schedules
|
|
3
|
+
module Spawn
|
|
4
|
+
def self.call: (Hash[String, String?] env, Array[String] argv, String chdir, ?forward_int: bool) -> [Integer?, bool]
|
|
5
|
+
def self.run: (Hash[String, String?] env, Array[String] argv, String chdir, Hash[Symbol, untyped] state, bool forward_int) -> Integer?
|
|
6
|
+
def self.stop: (Hash[Symbol, untyped] state, String signal, bool forward_int) -> void
|
|
7
|
+
def self.interrupt: (Hash[Symbol, untyped] state) -> void
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
module RubyRaider
|
|
2
|
-
module
|
|
3
|
-
class
|
|
4
|
-
DIR: String
|
|
2
|
+
module Schedules
|
|
3
|
+
class Store
|
|
5
4
|
FILE: String
|
|
6
5
|
NOFOLLOW: Integer
|
|
7
6
|
NONBLOCK: Integer
|
|
@@ -13,16 +12,22 @@ module RubyRaider
|
|
|
13
12
|
def initialize: (String name, String os_id) -> void
|
|
14
13
|
end
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
@project: Project
|
|
16
|
+
@schedule_class: untyped
|
|
17
|
+
@dir: String
|
|
18
|
+
@file: String
|
|
19
|
+
|
|
20
|
+
def initialize: (Project project, untyped schedule_class) -> void
|
|
21
|
+
def all: () -> Array[untyped]
|
|
22
|
+
def find: (String name) -> untyped
|
|
23
|
+
def save: (untyped schedule) -> untyped
|
|
20
24
|
def writable!: () -> nil
|
|
21
|
-
def delete: (String name) ->
|
|
25
|
+
def delete: (String name) -> untyped
|
|
22
26
|
|
|
23
27
|
private
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
attr_reader project: Project
|
|
30
|
+
def from_entry: (untyped entry) -> untyped
|
|
26
31
|
def named?: (untyped entry, String name) -> bool
|
|
27
32
|
def read_entries: () -> Array[untyped]?
|
|
28
33
|
def writable_entries: () -> Array[untyped]
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
require_relative '../../lib/ruby_raider'
|
|
6
|
+
require_relative '../../lib/commands/schedule_commands'
|
|
7
|
+
|
|
8
|
+
# Runs in this process through RubyRaider::Raider.start, so the `raider schedule` subcommand path is the real
|
|
9
|
+
# one (thor 1.2.2). Every example stubs Scheduler.new, RunWrapper.new and Legacy.new (or gives Legacy a
|
|
10
|
+
# recording adapter): nothing reaches the OS scheduler or spawns rspec.
|
|
11
|
+
RSpec.describe ScheduleCommands do
|
|
12
|
+
let(:scheduler) { instance_double(RubyRaider::Schedules::Scheduler, add: nil, list: nil, remove: nil) }
|
|
13
|
+
let(:wrapper) { instance_double(RubyRaider::Schedules::RunWrapper, run: 0) }
|
|
14
|
+
let(:legacy) { instance_double(RubyRaider::Schedules::Legacy, cleanup: nil) }
|
|
15
|
+
let(:dir) { File.realpath(Dir.mktmpdir('schedule-commands')) }
|
|
16
|
+
|
|
17
|
+
before do
|
|
18
|
+
allow(RubyRaider::Schedules::Scheduler).to receive(:new).and_return(scheduler)
|
|
19
|
+
allow(RubyRaider::Schedules::RunWrapper).to receive(:new).and_return(wrapper)
|
|
20
|
+
allow(RubyRaider::Schedules::Legacy).to receive(:new).and_return(legacy)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
after { FileUtils.rm_rf(dir) }
|
|
24
|
+
|
|
25
|
+
# [stdout, stderr, exit code or nil when the command did not exit]
|
|
26
|
+
def raider(*args)
|
|
27
|
+
out = StringIO.new
|
|
28
|
+
err = StringIO.new
|
|
29
|
+
stdout = $stdout
|
|
30
|
+
stderr = $stderr
|
|
31
|
+
$stdout = out
|
|
32
|
+
$stderr = err
|
|
33
|
+
code = begin
|
|
34
|
+
RubyRaider::Raider.start(args)
|
|
35
|
+
nil
|
|
36
|
+
rescue SystemExit => e
|
|
37
|
+
e.status
|
|
38
|
+
ensure
|
|
39
|
+
$stdout = stdout
|
|
40
|
+
$stderr = stderr
|
|
41
|
+
end
|
|
42
|
+
[out.string, err.string, code]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def project_root
|
|
46
|
+
satisfy { |project| project.root == dir }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe 'add' do
|
|
50
|
+
it 'takes the spec files and the options in any order' do
|
|
51
|
+
raider('schedule', 'add', 'nightly', '--every', 'day', 'spec/a_spec.rb', '--show-browser', 'spec/b_spec.rb',
|
|
52
|
+
'--project', dir)
|
|
53
|
+
|
|
54
|
+
expect(scheduler).to have_received(:add).with('nightly', every: 'day', files: %w[spec/a_spec.rb spec/b_spec.rb],
|
|
55
|
+
tag: nil, at: nil, on: nil, retries: 0, notify: false,
|
|
56
|
+
show_browser: true)
|
|
57
|
+
expect(RubyRaider::Schedules::Scheduler).to have_received(:new).with(project_root)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'passes --at, --on, --tag, --retries and --notify' do
|
|
61
|
+
raider('schedule', 'add', 'smoke', '--tag', 'smoke', '--every', 'week', '--on', 'mon', '--at', '08:30',
|
|
62
|
+
'--retries', '2', '--notify', '--project', dir)
|
|
63
|
+
|
|
64
|
+
expect(scheduler).to have_received(:add).with('smoke', every: 'week', files: [], tag: 'smoke', at: '08:30',
|
|
65
|
+
on: 'mon', retries: 2, notify: true, show_browser: false)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'is reached through raider sc' do
|
|
69
|
+
raider('sc', 'add', 'nightly', '--every', 'day', '--project', dir)
|
|
70
|
+
|
|
71
|
+
expect(scheduler).to have_received(:add).with('nightly', hash_including(every: 'day', files: []))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'exits 2 with one stderr line when --every is left out' do
|
|
75
|
+
# The real Scheduler refuses; its adapter is a double that fails on any call
|
|
76
|
+
allow(RubyRaider::Schedules::Scheduler).to receive(:new).and_wrap_original do |original, project|
|
|
77
|
+
original.call(project, adapter: instance_double(RubyRaider::Schedules::Os::Launchd), out: $stdout, err: $stderr)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
out, err, code = raider('schedule', 'add', 'nightly', '--at', '08:00', '--project', dir)
|
|
81
|
+
|
|
82
|
+
expect([out, err, code]).to eq(['', "Invalid every nil: use hour, day, weekday or week\n", 2])
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'exits 2 with one stderr line when the project has no Gemfile, before the OS scheduler is touched' do
|
|
86
|
+
allow(RubyRaider::Schedules::Scheduler).to receive(:new).and_wrap_original do |original, project|
|
|
87
|
+
original.call(project, adapter: instance_double(RubyRaider::Schedules::Os::Launchd), out: $stdout, err: $stderr)
|
|
88
|
+
end
|
|
89
|
+
version = File.read(File.expand_path('../../lib/version', __dir__)).strip
|
|
90
|
+
|
|
91
|
+
out, err, code = raider('schedule', 'add', 'nightly', '--every', 'day', '--at', '08:00', '--project', dir)
|
|
92
|
+
|
|
93
|
+
expect([out, err, code]).to eq(['', "Scheduling needs ruby_raider #{version} or later in this project: no Gemfile found\n", 2])
|
|
94
|
+
expect(File.exist?(File.join(dir, '.raider'))).to be(false)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe 'refusals' do
|
|
99
|
+
it 'exits 2 with the error on one stderr line' do
|
|
100
|
+
allow(scheduler).to receive(:remove).and_raise(RubyRaider::Schedules::Error, 'No schedule gone')
|
|
101
|
+
|
|
102
|
+
out, err, code = raider('schedule', 'remove', 'gone', '--project', dir)
|
|
103
|
+
|
|
104
|
+
expect([out, err, code]).to eq(['', "No schedule gone\n", 2])
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'keeps a --project with a newline on one stderr line' do
|
|
108
|
+
out, err, code = raider('schedule', 'list', '--project', "/nonexistent/a\nb")
|
|
109
|
+
|
|
110
|
+
expect([out, err, code]).to eq(['', "Project folder not found: /nonexistent/a?b\n", 2])
|
|
111
|
+
expect(RubyRaider::Schedules::Scheduler).not_to have_received(:new)
|
|
112
|
+
expect(RubyRaider::Schedules::Legacy).not_to have_received(:new)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'exits 2 for an argument that is not valid UTF-8, through schedule and sc' do
|
|
116
|
+
results = [raider('schedule', 'add', "a\xFF", '--every', 'day'), raider('sc', 'remove', "a\xFF")]
|
|
117
|
+
|
|
118
|
+
expect(results).to all(eq(['', "Invalid argument \"a\\xFF\": use valid UTF-8\n", 2]))
|
|
119
|
+
expect(RubyRaider::Schedules::Scheduler).not_to have_received(:new)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe 'list and remove' do
|
|
124
|
+
it 'lists as text, or as JSON with --json' do
|
|
125
|
+
raider('schedule', 'list', '--project', dir)
|
|
126
|
+
raider('schedule', 'list', '--json', '--project', dir)
|
|
127
|
+
|
|
128
|
+
expect(scheduler).to have_received(:list).with(json: false, notice: nil).ordered
|
|
129
|
+
expect(scheduler).to have_received(:list).with(json: true, notice: nil).ordered
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'removes by name' do
|
|
133
|
+
raider('schedule', 'remove', 'nightly', '--project', dir)
|
|
134
|
+
|
|
135
|
+
expect(scheduler).to have_received(:remove).with('nightly')
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
describe 'run' do
|
|
140
|
+
it 'maps run to the wrapper with the manual trigger by default' do
|
|
141
|
+
_out, _err, code = raider('schedule', 'run', 'nightly', '--project', dir)
|
|
142
|
+
|
|
143
|
+
expect(code).to eq(0)
|
|
144
|
+
expect(wrapper).to have_received(:run).with('nightly', trigger: 'manual')
|
|
145
|
+
expect(RubyRaider::Schedules::RunWrapper).to have_received(:new).with(project_root)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "exits with the wrapper's code and passes --trigger" do
|
|
149
|
+
codes = [1, 2, 3].map do |status|
|
|
150
|
+
allow(wrapper).to receive(:run).and_return(status)
|
|
151
|
+
raider('sc', 'run', 'nightly', '--trigger', 'schedule', '--project', dir).last
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
expect(codes).to eq([1, 2, 3])
|
|
155
|
+
expect(wrapper).to have_received(:run).with('nightly', trigger: 'schedule').exactly(3).times
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'exits 2 with one stderr line when the wrapper refuses' do
|
|
159
|
+
allow(wrapper).to receive(:run).and_raise(RubyRaider::Schedules::Error, 'Invalid trigger "x": use manual or schedule')
|
|
160
|
+
|
|
161
|
+
out, err, code = raider('schedule', 'run', 'nightly', '--trigger', 'x', '--project', dir)
|
|
162
|
+
|
|
163
|
+
expect([out, err, code]).to eq(['', "Invalid trigger \"x\": use manual or schedule\n", 2])
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe 'the 4.1 cleanup' do # rubocop:disable RSpec/MultipleMemoizedHelpers
|
|
168
|
+
# Plays the OS scheduler for Legacy: one 4.1 entry of this project, removed or refused
|
|
169
|
+
let(:fake_adapter_class) do
|
|
170
|
+
Class.new do
|
|
171
|
+
attr_reader :ids, :removed
|
|
172
|
+
attr_accessor :failure
|
|
173
|
+
|
|
174
|
+
def initialize(ids)
|
|
175
|
+
@ids = ids
|
|
176
|
+
@removed = []
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def list_ids(prefix) = ids.select { |id| id.start_with?(prefix) }
|
|
180
|
+
|
|
181
|
+
def remove(os_id)
|
|
182
|
+
raise failure if failure
|
|
183
|
+
|
|
184
|
+
@removed << os_id
|
|
185
|
+
true
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
let(:adapter) { fake_adapter_class.new([RubyRaider::Schedules::Legacy::OldRule.os_id_for(dir, 'tredssd')]) }
|
|
190
|
+
let(:notice) { "Removed 1 schedule made with Ruby Raider 4.1 (tredssd); it can't run in 4.2. Add it again with raider schedule add." }
|
|
191
|
+
|
|
192
|
+
before do
|
|
193
|
+
FileUtils.mkdir_p(File.join(dir, '.raider', 'flows'))
|
|
194
|
+
allow(RubyRaider::Schedules::Legacy).to receive(:new).and_wrap_original do |original, project|
|
|
195
|
+
original.call(project, adapter: adapter)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it 'runs before every command, with the project' do
|
|
200
|
+
allow(RubyRaider::Schedules::Legacy).to receive(:new).and_return(legacy)
|
|
201
|
+
[%w[add nightly --every day], %w[list], %w[remove nightly], %w[run nightly]].each do |args|
|
|
202
|
+
raider('schedule', *args, '--project', dir)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
expect(RubyRaider::Schedules::Legacy).to have_received(:new).with(project_root).exactly(4).times
|
|
206
|
+
expect(legacy).to have_received(:cleanup).with(no_args).exactly(4).times
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'cleans up before the command runs' do
|
|
210
|
+
allow(scheduler).to receive(:remove) { expect(adapter.removed.size).to eq(1) }
|
|
211
|
+
|
|
212
|
+
raider('schedule', 'remove', 'nightly', '--project', dir)
|
|
213
|
+
|
|
214
|
+
expect(scheduler).to have_received(:remove).with('nightly')
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# The fake keeps listing the entry, so the second list finds it again
|
|
218
|
+
it 'writes the notice to stderr in text mode, and passes it to the JSON' do
|
|
219
|
+
_out, err, code = raider('schedule', 'list', '--project', dir)
|
|
220
|
+
_json_out, json_err, = raider('schedule', 'list', '--json', '--project', dir)
|
|
221
|
+
|
|
222
|
+
expect([err, code, json_err]).to eq(["#{notice}\n", nil, "#{notice}\n"])
|
|
223
|
+
expect(scheduler).to have_received(:list).with(json: false, notice: notice).ordered
|
|
224
|
+
expect(scheduler).to have_received(:list).with(json: true, notice: notice).ordered
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it "keeps run's exit code when the cleanup fails" do
|
|
228
|
+
adapter.failure = RubyRaider::Schedules::Error.new('denied')
|
|
229
|
+
allow(wrapper).to receive(:run).and_return(1)
|
|
230
|
+
|
|
231
|
+
out, err, code = raider('schedule', 'run', 'nightly', '--project', dir)
|
|
232
|
+
|
|
233
|
+
expect([out, code]).to eq(['', 1])
|
|
234
|
+
expect(err).to eq("Could not remove 1 schedule made with Ruby Raider 4.1 (tredssd: denied); it is tried again next time.\n")
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it 'is skipped, with no adapter, when .raider/flows is not a folder' do
|
|
238
|
+
allow(RubyRaider::Schedules).to receive(:adapter)
|
|
239
|
+
FileUtils.rm_rf(File.join(dir, '.raider', 'flows'))
|
|
240
|
+
outside = File.realpath(Dir.mktmpdir('schedule-commands-outside'))
|
|
241
|
+
File.symlink(outside, File.join(dir, '.raider', 'flows'))
|
|
242
|
+
results = [raider('schedule', 'run', 'nightly', '--project', dir)]
|
|
243
|
+
FileUtils.rm_rf(File.join(dir, '.raider'))
|
|
244
|
+
results << raider('schedule', 'list', '--project', dir)
|
|
245
|
+
|
|
246
|
+
expect(results).to eq([['', '', 0], ['', '', nil]])
|
|
247
|
+
expect(RubyRaider::Schedules::Legacy).not_to have_received(:new)
|
|
248
|
+
expect(RubyRaider::Schedules).not_to have_received(:adapter)
|
|
249
|
+
ensure
|
|
250
|
+
FileUtils.rm_rf(outside) if outside
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
it 'prints nothing when there is nothing to clean' do
|
|
254
|
+
adapter.ids.clear
|
|
255
|
+
|
|
256
|
+
out, err, code = raider('schedule', 'run', 'nightly', '--project', dir)
|
|
257
|
+
|
|
258
|
+
expect([out, err, code]).to eq(['', '', 0])
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
describe 'help' do
|
|
263
|
+
it 'lists the four commands in raider help schedule' do
|
|
264
|
+
out, = raider('help', 'schedule')
|
|
265
|
+
|
|
266
|
+
expect(out).to include('schedule add NAME [SPEC_FILE ...]', 'schedule list', 'schedule remove NAME',
|
|
267
|
+
'schedule run NAME')
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Thor would also reach run_schedule from `run` by prefix; the map is what keeps `run` working if that changes
|
|
271
|
+
it 'maps run to run_schedule explicitly and shows it as run NAME' do
|
|
272
|
+
out, = raider('help', 'schedule')
|
|
273
|
+
|
|
274
|
+
expect(described_class.map).to include('run' => :run_schedule)
|
|
275
|
+
expect(out).to match(/^ +\S+ schedule run NAME +# Runs the schedule NAME now/)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it 'lists schedule in raider help' do
|
|
279
|
+
out, = raider('help')
|
|
280
|
+
|
|
281
|
+
expect(out).to include('schedule', 'Runs the tests on a schedule')
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'content_helper'
|
|
4
|
+
require_relative '../../../lib/utilities/schedules/schedule_store'
|
|
4
5
|
|
|
5
6
|
describe 'Common file content' do
|
|
6
7
|
# --- Rakefile ---
|
|
@@ -77,6 +78,10 @@ describe 'Common file content' do
|
|
|
77
78
|
it 'includes raider-results' do
|
|
78
79
|
expect(gitignore).to include('raider-results')
|
|
79
80
|
end
|
|
81
|
+
|
|
82
|
+
it 'ignores .raider/schedules/ with the exact line raider schedule add looks for' do
|
|
83
|
+
expect(gitignore.lines.map(&:chomp)).to include(RubyRaider::Schedules::ScheduleStore::GITIGNORE_LINE)
|
|
84
|
+
end
|
|
80
85
|
end
|
|
81
86
|
|
|
82
87
|
shared_examples 'web gitignore for saved logins' do |project_name|
|
|
@@ -89,8 +94,9 @@ describe 'Common file content' do
|
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
shared_examples 'mobile gitignore without saved logins' do |project_name|
|
|
92
|
-
it 'has no .raider line' do
|
|
93
|
-
|
|
97
|
+
it 'has no .raider line but .raider/schedules/' do
|
|
98
|
+
lines = read_generated(project_name, '.gitignore').lines.map(&:strip)
|
|
99
|
+
expect(lines.grep(/\A\.raider/)).to eq(['.raider/schedules/'])
|
|
94
100
|
end
|
|
95
101
|
end
|
|
96
102
|
|
|
@@ -85,8 +85,8 @@ describe 'Gemfile content' do
|
|
|
85
85
|
expect(gemfile).to include("gem 'ruby_raider', '~> #{major_minor}'")
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
it 'pins ruby_raider to the 4.
|
|
89
|
-
expect(gemfile).to include("gem 'ruby_raider', '~> 4.
|
|
88
|
+
it 'pins ruby_raider to the 4.2 release line' do
|
|
89
|
+
expect(gemfile).to include("gem 'ruby_raider', '~> 4.2'")
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
end
|
|
@@ -447,69 +447,6 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
447
447
|
end
|
|
448
448
|
end
|
|
449
449
|
|
|
450
|
-
# raider flow init and raider flow run on a generated Selenium project, headless, against a local page
|
|
451
|
-
context 'Flows', :slow do
|
|
452
|
-
let(:project) { generate_video_project('rspec', 'selenium') }
|
|
453
|
-
|
|
454
|
-
def write_flows(project)
|
|
455
|
-
page = File.join(File.expand_path(project), 'flow_page.html')
|
|
456
|
-
File.write(page, <<~HTML)
|
|
457
|
-
<!doctype html><title>Timesheet</title>
|
|
458
|
-
<label for="hours">Hours</label><input id="hours">
|
|
459
|
-
<button onclick="document.getElementById('done').textContent = 'Saved ' + document.getElementById('hours').value">Save</button>
|
|
460
|
-
<p id="done"></p>
|
|
461
|
-
HTML
|
|
462
|
-
{ 'timesheet' => "driver.find_element(label: 'Hours').send_keys(flow_input('hours', '8'))\n " \
|
|
463
|
-
"driver.find_element(by_role('button', name: 'Save')).click\n " \
|
|
464
|
-
"driver.find_element(text: \"Saved \#{flow_input('hours', '8')}\")",
|
|
465
|
-
'export' => "driver.find_element(id: 'export').click" }.each do |name, steps|
|
|
466
|
-
File.write(File.join(project, "flows/#{name}_flow.rb"), <<~RUBY)
|
|
467
|
-
# frozen_string_literal: true
|
|
468
|
-
|
|
469
|
-
require_relative 'flow_helper'
|
|
470
|
-
|
|
471
|
-
RSpec.describe '#{name}' do
|
|
472
|
-
it 'runs' do
|
|
473
|
-
driver.navigate.to 'file://#{page}'
|
|
474
|
-
#{steps}
|
|
475
|
-
end
|
|
476
|
-
end
|
|
477
|
-
RUBY
|
|
478
|
-
end
|
|
479
|
-
end
|
|
480
|
-
|
|
481
|
-
def runs(project, name)
|
|
482
|
-
File.readlines(File.join(project, ".raider/flows/#{name}/runs.jsonl")).map { |line| JSON.parse(line) }
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
it 'runs a flow headless into flow-results and records completed and failed runs', :aggregate_failures do
|
|
486
|
-
skip 'Google Chrome is not installed' unless browser_installed?('chrome')
|
|
487
|
-
FileUtils.rm_rf([File.join(project, 'flows'), File.join(project, '.raider/flows'), File.join(project, 'flow-results')])
|
|
488
|
-
|
|
489
|
-
init = run_in_project(project, 'bundle exec raider flow init')
|
|
490
|
-
expect(init[:exit_code]).to eq(0), init[:stdout] + init[:stderr]
|
|
491
|
-
write_flows(project)
|
|
492
|
-
env = { 'VIDEO' => 'off', 'TRACE' => 'off', 'RAIDER_TOKEN' => nil, 'RAIDER_ENDPOINT' => nil }
|
|
493
|
-
completed = run_in_project(project, 'bundle exec raider flow run timesheet --input hours=7', env:)
|
|
494
|
-
failed = run_in_project(project, 'bundle exec raider flow run export', env: env.merge('RAIDER_TIMEOUT' => '1'))
|
|
495
|
-
|
|
496
|
-
expect(completed[:exit_code]).to eq(0), completed[:stdout] + completed[:stderr]
|
|
497
|
-
expect(completed[:stdout]).to match(%r{Flow timesheet completed in .* s\nResults: flow-results/timesheet/\d{8}T\d{6}Z})
|
|
498
|
-
expect(runs(project, 'timesheet').last).to include('status' => 'completed', 'trigger' => 'manual')
|
|
499
|
-
expect(failed[:exit_code]).to eq(1), failed[:stdout] + failed[:stderr]
|
|
500
|
-
expect(runs(project, 'export').last).to include('status' => 'failed', 'failed_step' => { 'title' => 'runs', 'line' => 8 })
|
|
501
|
-
expect(File).to exist(File.join(project, runs(project, 'export').last['results_dir'], 'run.json'))
|
|
502
|
-
expect(File).not_to exist(File.join(project, 'raider-results'))
|
|
503
|
-
end
|
|
504
|
-
|
|
505
|
-
it 'passes its own RuboCop with the flow files' do
|
|
506
|
-
FileUtils.rm_rf(File.join(project, 'flows'))
|
|
507
|
-
run_in_project(project, 'bundle exec raider flow init')
|
|
508
|
-
|
|
509
|
-
expect_project_rubocop_clean(project)
|
|
510
|
-
end
|
|
511
|
-
end
|
|
512
|
-
|
|
513
450
|
# Trace replay against headless Chrome, and one example on headless Firefox. The page is served over HTTP from this process (never file://), so
|
|
514
451
|
# WebDriver BiDi reports real network and console entries. raider_reporter ~> 1.0 resolves from RubyGems, or
|
|
515
452
|
# from a checkout at RAIDER_REPORTER_PATH. The example with video on also needs RAIDER_FFMPEG_PATH.
|