probatio_diabolica 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 891d8275e2ccd96dea0d74c456a55693ab667fd556fedf9b5d1fe9470b0949cb
4
- data.tar.gz: d53faf24d75c1f112e5cb064048372d7ac7c5391f50ed3e546a5f7990cb7d02f
3
+ metadata.gz: 39336802d7f3c344f21acbf0567ea85a73dfbc68c2d339e7ab5e1e1866fc609e
4
+ data.tar.gz: c4f0fa5209ac0a7437edf9e6cf117b95fe859217b49e172557f05c44dc664307
5
5
  SHA512:
6
- metadata.gz: faafc8c84e60b7058d57ceb6d457502822724aea22325e3e0ccd4efb3e3765903f21f8525f0e4c9937bea8e69901d39e543796a776b5a1305922492955ce373e
7
- data.tar.gz: 4d9f2a94aee2e50112544aa6c0be783ee79f95ee0b4dd7ef95731590b45f335a4e2e61044a99bfa4d2b6125767c9f9b6f91ba2b7e8b70e275ab792b62b138be3
6
+ metadata.gz: f78a5ff31c1b5cbee275d596ef74529b0fad41426c4b7ee5303d98d4224cbcf20718485403ec048fc4f26159775c0ae8e33c07eeeb95e6944c2a21cf3feeec26
7
+ data.tar.gz: ffa412e8bc62906e1c9be463a9d48dc9a30bbf4e4fcf458abc1e203026bde0a7e5f60ae0dce03c33cd6375c5402060b80ae1b1cf0b610328b1808a5eb436b8cf
data/README.md CHANGED
@@ -161,6 +161,42 @@ end
161
161
  - `expect { |subject| ... }.to matcher`
162
162
  - `expect.to matcher` (uses `subject`)
163
163
 
164
+ ### Spec best practices for `subject` (PRD reports)
165
+
166
+ When a test defines a `subject`, PRD can surface it more clearly in generated reports.
167
+ For CLI and integration specs, prefer:
168
+
169
+ - grouping with explicit `context`
170
+ - one `subject` per context for the main action
171
+ - assertions written with `expect.to(...)` when the assertion targets `subject`
172
+
173
+ Example:
174
+
175
+ ```ruby
176
+ context 'when CLI receives an unknown formatter type' do
177
+ subject { Open3.capture3('bundle exec ruby bin/prd spec/self_hosted_spec.rb -t unknown') }
178
+
179
+ it 'fails fast on unknown formatter type in CLI' do
180
+ _stdout, stderr, status = subject
181
+
182
+ expect(status.success?).to(be(false))
183
+ expect(stderr).to(includes('Unsupported formatter type: unknown. Supported: simple, html, json, pdf'))
184
+ end
185
+ end
186
+ ```
187
+
188
+ For simple value checks, this pattern keeps specs concise:
189
+
190
+ ```ruby
191
+ context 'with strings' do
192
+ subject { 'probatio diabolica' }
193
+
194
+ it 'matches expected content' do
195
+ expect.to(includes('diabolica'))
196
+ end
197
+ end
198
+ ```
199
+
164
200
  ### Matchers
165
201
 
166
202
  - `eq(expected)` equality with `==`
@@ -174,13 +210,30 @@ end
174
210
 
175
211
  `PrD::Runtime` exposes helpers to test content loaded in Chrome:
176
212
 
213
+ - `page(at:, warmup_time:)` opens a page and returns a `BrowserSession`
177
214
  - `screen(at:, width:, height:, warmup_time:)` captures a PNG and returns a `File`
178
- - `text(at:, css:, warmup_time:)` extracts a CSS node into a `.txt` file and returns a `File`
215
+ - `text(at:, css:, warmup_time:)` extracts a CSS node and returns `PrD::Code` (language: `text`)
179
216
  - `network(at:, warmup_time:)` returns Ferrum network traffic
180
217
  - `network_urls(at:, warmup_time:)` returns traffic URLs
181
218
  - `pdf(at:, warmup_time:)` generates a PDF and returns a `PDF::Reader`
182
219
  - `html(at:, warmup_time:)` returns HTML (`browser.body`)
183
220
 
221
+ `BrowserSession` adds high-level page interactions:
222
+
223
+ - `find(css:/xpath:, wait:, shadow:)`
224
+ - `exists?(css:/xpath:, wait:, shadow:)`
225
+ - `click(css:/xpath:, wait:, shadow:)`
226
+ - `fill(css:/xpath:, with:, clear:, blur:, wait:, shadow:)`
227
+ - `select_option(css:, value:/values:, by:, wait:, shadow:)`
228
+ - `set_files(css:, path:/paths:, wait:, shadow:)` (alias `upload_files`)
229
+ - `navigate(to:, warmup_time:)`
230
+
231
+ About `shadow:`:
232
+
233
+ - `shadow:` is an ordered CSS path used to narrow the scope before the target selector.
234
+ - Each step can be a shadow host or a regular container.
235
+ - If a step has `shadowRoot`, search continues inside it; otherwise search continues inside the matched node.
236
+
184
237
  Prerequisites:
185
238
 
186
239
  - Chrome/Chromium must be installed.
@@ -197,6 +250,22 @@ it 'checks dynamic content loaded in browser' do
197
250
  end
198
251
  ```
199
252
 
253
+ Form interaction and file upload example:
254
+
255
+ ```ruby
256
+ it 'uploads a file in a shadow-dom form' do
257
+ html(at: 'https://example.com/upload', warmup_time: 2) do |page|
258
+ page.click(css: 'button[data-open-upload]')
259
+ page.fill(css: 'input[name="title"]', with: 'Invoice')
260
+ page.set_files(
261
+ css: 'input[type="file"]',
262
+ shadow: ['vax-scanner', '[data-view="upload"]'],
263
+ path: 'examples/random_photo.png'
264
+ )
265
+ end
266
+ end
267
+ ```
268
+
200
269
  ### Source code helper (Prism)
201
270
 
202
271
  `source_code(...)` uses the `prism` gem to parse Ruby source and extract class/method code.
@@ -256,12 +325,12 @@ When you define a `subject`, each formatter tries to render it in the most usefu
256
325
  - for files, prints a textual representation (for example path, file preview for `.txt`)
257
326
  - `HtmlFormatter`:
258
327
  - renders text values directly
259
- - for `PrD::Code`, renders syntax-highlighted code blocks (Rouge)
328
+ - for `PrD::Code`, renders syntax-highlighted code blocks (Rouge) inside collapsible sections
260
329
  - for image files (`.png`, `.jpg`, `.jpeg`), embeds the image in the report
261
330
  - for PDF subjects (`File` `.pdf` or `PDF::Reader`), embeds the PDF with a `data:application/pdf;base64,...` URI
262
331
  - `PdfFormatter`:
263
332
  - renders text values as report lines
264
- - for `PrD::Code`, renders language + code block (plain, without syntax colors)
333
+ - for `PrD::Code`, renders language + syntax-highlighted code block (Rouge -> Prawn colors)
265
334
  - for image files (`.png`, `.jpg`, `.jpeg`), inserts the image directly in the PDF report
266
335
  - `JsonFormatter`:
267
336
  - keeps a structured representation for machine processing
data/lib/pr_d/code.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  module PrD
2
- class Code
2
+ class Code < SimpleDelegator
3
3
  attr_reader :source, :language
4
4
 
5
5
  def initialize(source:, language: 'ruby')
6
+ super(source.to_s)
6
7
  @source = source.to_s
7
8
  @language = language.to_s
8
9
  end
@@ -357,19 +357,49 @@ module PrD
357
357
  .code-language {
358
358
  color: var(--muted);
359
359
  font-size: 0.85rem;
360
- margin-bottom: 0.35rem;
361
360
  text-transform: uppercase;
362
361
  letter-spacing: 0.05em;
363
362
  }
364
363
 
365
364
  .code-block {
366
365
  margin: 0.45rem 0 0.7rem;
366
+ border: 1px solid var(--line);
367
+ border-radius: 10px;
368
+ background: #fff;
369
+ }
370
+
371
+ .code-toggle {
372
+ list-style: none;
373
+ cursor: pointer;
374
+ padding: 0.6rem 0.8rem;
375
+ display: flex;
376
+ align-items: center;
377
+ justify-content: space-between;
378
+ gap: 0.8rem;
379
+ font-size: 0.88rem;
380
+ color: var(--text);
381
+ }
382
+
383
+ .code-toggle::-webkit-details-marker {
384
+ display: none;
385
+ }
386
+
387
+ .code-toggle::after {
388
+ content: 'Open';
389
+ color: var(--muted);
390
+ font-size: 0.78rem;
391
+ letter-spacing: 0.03em;
392
+ text-transform: uppercase;
393
+ }
394
+
395
+ .code-block[open] .code-toggle::after {
396
+ content: 'Close';
367
397
  }
368
398
 
369
399
  .highlight {
370
400
  margin: 0;
371
- border: 1px solid var(--line);
372
- border-radius: 10px;
401
+ border-top: 1px solid var(--line);
402
+ border-radius: 0 0 10px 10px;
373
403
  overflow-x: auto;
374
404
  }
375
405
 
@@ -515,10 +545,12 @@ module PrD
515
545
  language = normalize_text(code.language)
516
546
  highlighted = highlight_code(source, language)
517
547
  <<~HTML
518
- <div class="code-block">
519
- <div class="code-language">#{escape(language)}</div>
548
+ <details class="code-block">
549
+ <summary class="code-toggle">
550
+ <span class="code-language">#{escape(language)}</span>
551
+ </summary>
520
552
  #{highlighted}
521
- </div>
553
+ </details>
522
554
  HTML
523
555
  end
524
556
 
@@ -1,4 +1,5 @@
1
1
  require 'prawn'
2
+ require 'rouge'
2
3
 
3
4
  module PrD
4
5
  module Formatters
@@ -76,7 +77,7 @@ module PrD
76
77
  add_event(:subject, message: 'Subject', level: @level)
77
78
  if code_object?(subject)
78
79
  add_event(:code_header, message: "Language: #{subject.language}", level: @level + 1)
79
- add_event(:code_block, message: subject.source, level: @level + 1)
80
+ add_event(:code_block, message: subject.source, level: @level + 1, language: subject.language)
80
81
  elsif image_file?(subject)
81
82
  add_event(:detail, message: serialize(subject).to_s, level: @level + 1)
82
83
  add_event(:subject_image, message: subject.path, level: @level + 1)
@@ -98,7 +99,7 @@ module PrD
98
99
  return if synthetic?
99
100
  if code_object?(expectation)
100
101
  add_event(:code_header, message: "Expect (#{expectation.language})", level: @level + 1)
101
- add_event(:code_block, message: expectation.source, level: @level + 1)
102
+ add_event(:code_block, message: expectation.source, level: @level + 1, language: expectation.language)
102
103
  else
103
104
  add_event(:detail, message: "Expect: #{serialize(expectation)}", level: @level + 1)
104
105
  end
@@ -153,13 +154,13 @@ module PrD
153
154
 
154
155
  private
155
156
 
156
- def add_event(type, message:, level:, anchor_id: nil)
157
+ def add_event(type, message:, level:, anchor_id: nil, **extra)
157
158
  @events << {
158
159
  type:,
159
160
  message: safe_pdf_text(message.to_s),
160
161
  level: [level, 0].max,
161
162
  anchor_id:
162
- }
163
+ }.merge(extra)
163
164
  end
164
165
 
165
166
  def add_index_entry(type:, label:, level:, anchor_id:)
@@ -177,6 +178,43 @@ module PrD
177
178
  .encode('UTF-8')
178
179
  end
179
180
 
181
+ def rouge_lexer_for(source, language)
182
+ Rouge::Lexer.find_fancy(language.to_s, source.to_s) || Rouge::Lexers::PlainText
183
+ rescue StandardError
184
+ Rouge::Lexers::PlainText
185
+ end
186
+
187
+ def rouge_token_color(token)
188
+ qualname = token.qualname.to_s
189
+
190
+ return '6B7280' if qualname.start_with?('Comment')
191
+ return 'C2410C' if qualname.start_with?('Keyword', 'Operator')
192
+ return '1D4ED8' if qualname.start_with?('Name.Function', 'Name.Class', 'Name.Builtin')
193
+ return '047857' if qualname.start_with?('Literal.String')
194
+ return '7C3AED' if qualname.start_with?('Literal.Number')
195
+
196
+ COLORS[:text]
197
+ end
198
+
199
+ def highlighted_code_fragments(source, language)
200
+ lexer = rouge_lexer_for(source, language)
201
+ fragments = lexer.lex(source.to_s).filter_map do |token, value|
202
+ next if value.nil? || value.empty?
203
+
204
+ {
205
+ text: safe_pdf_text(value),
206
+ color: rouge_token_color(token),
207
+ font: 'Courier'
208
+ }
209
+ end
210
+
211
+ return fragments unless fragments.empty?
212
+
213
+ [{ text: safe_pdf_text(source.to_s), color: COLORS[:text], font: 'Courier' }]
214
+ rescue StandardError
215
+ [{ text: safe_pdf_text(source.to_s), color: COLORS[:text], font: 'Courier' }]
216
+ end
217
+
180
218
  def render_header(document)
181
219
  document.fill_color COLORS[:title]
182
220
  document.text 'Probatio Diabolica', size: 20, style: :bold
@@ -238,7 +276,7 @@ module PrD
238
276
  when :code_header
239
277
  styled_line(document, event[:message], level: event[:level], size: 10, style: :bold, color: COLORS[:muted])
240
278
  when :code_block
241
- render_code_block(document, event[:message], level: event[:level])
279
+ render_code_block(document, event[:message], level: event[:level], language: event[:language])
242
280
  when :detail, :subject, :justification
243
281
  styled_line(document, event[:message], level: event[:level], size: 10, color: COLORS[:text])
244
282
  when :subject_image
@@ -296,12 +334,11 @@ module PrD
296
334
  document.move_down 2
297
335
  end
298
336
 
299
- def render_code_block(document, text, level:)
337
+ def render_code_block(document, text, level:, language: nil)
300
338
  document.indent(level * 14) do
301
339
  document.fill_color COLORS[:muted]
302
340
  document.text '--- Code Block ---', size: 9, style: :italic
303
- document.fill_color COLORS[:text]
304
- document.font('Courier') { document.text text, size: 9 }
341
+ document.formatted_text(highlighted_code_fragments(text, language), size: 9)
305
342
  document.fill_color COLORS[:muted]
306
343
  document.text '--- End Block ---', size: 9, style: :italic
307
344
  document.fill_color COLORS[:text]
@@ -312,7 +349,7 @@ module PrD
312
349
  def add_matcher_value_event(label, value)
313
350
  if code_object?(value)
314
351
  add_event(:matcher, message: "#{label} (#{value.language})", level: @level + 2)
315
- add_event(:code_block, message: value.source, level: @level + 2)
352
+ add_event(:code_block, message: value.source, level: @level + 2, language: value.language)
316
353
  else
317
354
  add_event(:matcher, message: "#{label}: #{serialize(value)}", level: @level + 2)
318
355
  end
@@ -5,10 +5,199 @@ require 'pdf-reader'
5
5
  module PrD
6
6
  module Helpers
7
7
  module ChromeHelper
8
+ class BrowserSession
9
+ SHADOW_QUERY_FUNCTION = <<~JS.freeze
10
+ function(shadowSelectors, targetSelector, within) {
11
+ let scope = within || document;
12
+
13
+ for (const scopeSelector of shadowSelectors) {
14
+ if (!scope || typeof scope.querySelector !== "function") return null;
15
+ const node = scope.querySelector(scopeSelector);
16
+ if (!node) return null;
17
+ scope = node.shadowRoot || node;
18
+ }
19
+
20
+ if (!scope || typeof scope.querySelector !== "function") return null;
21
+ return scope.querySelector(targetSelector);
22
+ }
23
+ JS
24
+
25
+ def initialize(browser, poll_interval: 0.05)
26
+ @browser = browser
27
+ @poll_interval = poll_interval
28
+ end
29
+
30
+ attr_reader :browser
31
+
32
+ def navigate(to:, warmup_time: 0)
33
+ @browser.go_to(to)
34
+ sleep(warmup_time.to_f) if warmup_time.to_f.positive?
35
+ self
36
+ end
37
+
38
+ def wait(seconds)
39
+ sleep(seconds.to_f) if seconds.to_f.positive?
40
+ self
41
+ end
42
+
43
+ def wait_for(css: nil, xpath: nil, within: nil, shadow: nil, timeout: 2)
44
+ find(css:, xpath:, within:, shadow:, wait: timeout)
45
+ end
46
+
47
+ def exists?(css: nil, xpath: nil, within: nil, shadow: nil, wait: 0)
48
+ !find(css:, xpath:, within:, shadow:, wait:, raise_on_missing: false).nil?
49
+ end
50
+
51
+ def find(css: nil, xpath: nil, within: nil, shadow: nil, wait: 2, raise_on_missing: true)
52
+ selector = normalize_selector(css:, xpath:)
53
+ node = find_with_wait(selector:, within:, shadow:, wait:)
54
+ return node if node
55
+ return nil unless raise_on_missing
56
+
57
+ raise ArgumentError, "Selector not found: #{selector[:label]}"
58
+ end
59
+
60
+ def click(css: nil, xpath: nil, within: nil, shadow: nil, wait: 2, mode: :left, keys: [], offset: {}, delay: 0)
61
+ node = find(css:, xpath:, within:, shadow:, wait:)
62
+ node.scroll_into_view
63
+ node.click(mode:, keys:, offset:, delay:)
64
+ node
65
+ end
66
+
67
+ def fill(css: nil, xpath: nil, within: nil, shadow: nil, with:, wait: 2, clear: true, blur: false, dispatch_events: true)
68
+ node = find(css:, xpath:, within:, shadow:, wait:)
69
+ node.focus
70
+ node.evaluate("this.value = ''") if clear
71
+ node.type(with.to_s)
72
+ dispatch_input_events(node) if dispatch_events
73
+ node.blur if blur
74
+ node
75
+ end
76
+
77
+ def select_option(css:, within: nil, shadow: nil, value: nil, values: nil, by: :value, wait: 2)
78
+ node = find(css:, within:, shadow:, wait:)
79
+ option_values = Array(values || value).flatten.compact
80
+ raise ArgumentError, 'select_option requires `value` or `values`.' if option_values.empty?
81
+
82
+ node.select(*option_values, by:)
83
+ node
84
+ end
85
+
86
+ def set_files(css:, within: nil, shadow: nil, path: nil, paths: nil, wait: 2, dispatch_events: true)
87
+ node = find(css:, within:, shadow:, wait:)
88
+ file_paths = normalize_file_paths(path:, paths:)
89
+ node.select_file(file_paths)
90
+ dispatch_input_events(node) if dispatch_events
91
+ node
92
+ end
93
+ alias upload_files set_files
94
+
95
+ def method_missing(method_name, *args, **kwargs, &block)
96
+ return @browser.public_send(method_name, *args, **kwargs, &block) if @browser.respond_to?(method_name)
97
+
98
+ super
99
+ end
100
+
101
+ def respond_to_missing?(method_name, include_private = false)
102
+ @browser.respond_to?(method_name, include_private) || super
103
+ end
104
+
105
+ private
106
+
107
+ def normalize_selector(css:, xpath:)
108
+ css = normalize_optional_selector(css)
109
+ xpath = normalize_optional_selector(xpath)
110
+
111
+ if css.nil? && xpath.nil?
112
+ raise ArgumentError, 'Provide a selector with `css:` or `xpath:`.'
113
+ end
114
+
115
+ if css && xpath
116
+ raise ArgumentError, 'Use either `css:` or `xpath:`, not both.'
117
+ end
118
+
119
+ if xpath && !xpath.nil?
120
+ { type: :xpath, value: xpath, label: "xpath=#{xpath}" }
121
+ else
122
+ { type: :css, value: css, label: "css=#{css}" }
123
+ end
124
+ end
125
+
126
+ def normalize_optional_selector(value)
127
+ return nil if value.nil?
128
+
129
+ selector = value.to_s.strip
130
+ return nil if selector.empty?
131
+
132
+ selector
133
+ end
134
+
135
+ def find_with_wait(selector:, within:, shadow:, wait:)
136
+ timeout = wait.to_f
137
+ timeout = 0 if timeout.negative?
138
+ deadline = monotonic_now + timeout
139
+
140
+ loop do
141
+ node = resolve_node(selector:, within:, shadow:)
142
+ return node if node
143
+
144
+ break if monotonic_now >= deadline
145
+
146
+ sleep(@poll_interval)
147
+ end
148
+
149
+ nil
150
+ end
151
+
152
+ def resolve_node(selector:, within:, shadow:)
153
+ case selector[:type]
154
+ when :xpath
155
+ raise ArgumentError, '`shadow:` can only be used with `css:` selectors.' if shadow && !Array(shadow).empty?
156
+
157
+ within ? within.at_xpath(selector[:value]) : @browser.at_xpath(selector[:value])
158
+ when :css
159
+ if shadow && !Array(shadow).empty?
160
+ @browser.evaluate_func(SHADOW_QUERY_FUNCTION, Array(shadow), selector[:value], within)
161
+ elsif within
162
+ within.at_css(selector[:value])
163
+ else
164
+ @browser.at_css(selector[:value])
165
+ end
166
+ end
167
+ end
168
+
169
+ def dispatch_input_events(node)
170
+ node.evaluate("this.dispatchEvent(new Event('input', { bubbles: true }))")
171
+ node.evaluate("this.dispatchEvent(new Event('change', { bubbles: true }))")
172
+ end
173
+
174
+ def normalize_file_paths(path:, paths:)
175
+ raw_paths = Array(paths || path).flatten.compact.map { |value| value.to_s.strip }.reject(&:empty?)
176
+ raise ArgumentError, 'set_files requires `path` or `paths`.' if raw_paths.empty?
177
+
178
+ expanded_paths = raw_paths.map { |file_path| File.expand_path(file_path, Dir.pwd) }
179
+ missing = expanded_paths.reject { |file_path| File.exist?(file_path) }
180
+ raise ArgumentError, "File not found: #{missing.join(', ')}" unless missing.empty?
181
+
182
+ expanded_paths
183
+ end
184
+
185
+ def monotonic_now
186
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
187
+ end
188
+ end
189
+
190
+ def page(at:, warmup_time: 2)
191
+ session = prepare_browser_session(at:, warmup_time:)
192
+ yield session if block_given?
193
+ session
194
+ end
195
+
8
196
  def screen(at:, width: 1280, height: 800, warmup_time: 2)
9
- browser = prepare_browser(at:, warmup_time:)
197
+ session = prepare_browser_session(at:, warmup_time:)
198
+ browser = session.browser
10
199
  browser.set_viewport(width:, height:)
11
- yield browser if block_given?
200
+ yield session if block_given?
12
201
 
13
202
  screenshot_id = Digest::SHA256.hexdigest("#{at}-#{width}-#{height}-#{warmup_time}")
14
203
  file_name = File.join(chrome_annex_dir, "screenshot-#{screenshot_id}.png")
@@ -17,19 +206,19 @@ module PrD
17
206
  end
18
207
 
19
208
  def text(at:, css: 'body', warmup_time: 2)
20
- browser = prepare_browser(at:, warmup_time:)
21
- yield browser if block_given?
209
+ session = prepare_browser_session(at:, warmup_time:)
210
+ yield session if block_given?
22
211
 
23
- text_node = browser.at_css(css)
212
+ text_node = session.find(css:, wait: 0, raise_on_missing: false)
24
213
  raise ArgumentError, "CSS selector not found: #{css}" unless text_node
25
214
 
26
215
  PrD::Code.new(source: text_node.text, language: 'text')
27
216
  end
28
217
 
29
218
  def network(at:, warmup_time: 2)
30
- browser = prepare_browser(at:, warmup_time:)
31
- yield browser if block_given?
32
- browser.network.traffic
219
+ session = prepare_browser_session(at:, warmup_time:)
220
+ yield session if block_given?
221
+ session.browser.network.traffic
33
222
  end
34
223
 
35
224
  def network_urls(at:, warmup_time: 2, &block)
@@ -37,8 +226,9 @@ module PrD
37
226
  end
38
227
 
39
228
  def pdf(at:, warmup_time: 2)
40
- browser = prepare_browser(at:, warmup_time:)
41
- yield browser if block_given?
229
+ session = prepare_browser_session(at:, warmup_time:)
230
+ browser = session.browser
231
+ yield session if block_given?
42
232
 
43
233
  pdf_id = Digest::SHA256.hexdigest(at)
44
234
  file_name = File.join(chrome_annex_dir, "pdf-#{pdf_id}.pdf")
@@ -47,10 +237,10 @@ module PrD
47
237
  end
48
238
 
49
239
  def html(at:, warmup_time: 2)
50
- browser = prepare_browser(at:, warmup_time:)
51
- yield browser if block_given?
240
+ session = prepare_browser_session(at:, warmup_time:)
241
+ yield session if block_given?
52
242
 
53
- PrD::Code.new(source: browser.body, language: 'html')
243
+ PrD::Code.new(source: session.browser.body, language: 'html')
54
244
  end
55
245
 
56
246
  def close_chrome_browser
@@ -68,6 +258,11 @@ module PrD
68
258
  @browser ||= Ferrum::Browser.new
69
259
  end
70
260
 
261
+ def prepare_browser_session(at:, warmup_time:)
262
+ browser = prepare_browser(at:, warmup_time:)
263
+ BrowserSession.new(browser)
264
+ end
265
+
71
266
  def prepare_browser(at:, warmup_time:)
72
267
  browser = chrome_browser
73
268
  browser.go_to(at)
data/lib/pr_d/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PrD
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: probatio_diabolica
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Laporte Mathieu