bidi2pdf 0.1.8 → 0.1.9
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 +30 -1
- data/docker/Dockerfile.chromedriver +23 -5
- data/docker/entrypoint.sh +41 -0
- data/lib/bidi2pdf/bidi/browser_tab.rb +22 -11
- data/lib/bidi2pdf/bidi/client.rb +7 -5
- data/lib/bidi2pdf/bidi/event_manager.rb +34 -4
- data/lib/bidi2pdf/bidi/interceptor.rb +12 -2
- data/lib/bidi2pdf/bidi/session.rb +2 -0
- data/lib/bidi2pdf/bidi/web_socket_dispatcher.rb +5 -5
- data/lib/bidi2pdf/chromedriver_manager.rb +25 -11
- data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_container.rb +0 -6
- data/lib/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rb +103 -0
- data/lib/bidi2pdf/test_helpers/testcontainers/shared_docker_network.rb +21 -0
- data/lib/bidi2pdf/test_helpers/testcontainers/testcontainers_refinement.rb +53 -0
- data/lib/bidi2pdf/test_helpers/testcontainers.rb +17 -0
- data/lib/bidi2pdf/version.rb +1 -1
- data/sig/bidi2pdf/bidi/event_manager.rbs +19 -13
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d7fa3c853f53e21a110cadb25fcd66bf97b890eb00663f90e73e8ee9d1ce07e
|
4
|
+
data.tar.gz: 5900568c47f526e9b00d95a15f8293abeaee88f6c0f323bc6e79cdcb3aba38ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ecffa81a6358c413dd24b1cef48c3b2282518f5934710558b2ea834a362b198d5a1e3f01a2b3eb8ed163dedffaae61fe2bb0c5f3edec5e3850ad375daf10304
|
7
|
+
data.tar.gz: 0b9d4b19f9d2d01a8babfe2a13c4256b9f93d34f2fcdc1ea2942fb780ce4ad02962050f1adffd9d2c5731e2e6c6d1dd83deaa3d31b348157197c982701a8995b
|
data/CHANGELOG.md
CHANGED
@@ -11,6 +11,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
11
11
|
|
12
12
|
<!-- generated by git-cliff end -->
|
13
13
|
|
14
|
+
## [0.1.9] - 2025-05-04
|
15
|
+
|
16
|
+
### 🎨 Refactored
|
17
|
+
|
18
|
+
- Enhance reader thread management in Chromedriver by @dieter-medium
|
19
|
+
- Improve event listener logging by @dieter-medium
|
20
|
+
- Improve event listener management by @dieter-medium
|
21
|
+
- Centralize chromedriver test helpers for reuse in other projects by @dieter-medium
|
22
|
+
|
23
|
+
### 🐛 Fixed
|
24
|
+
|
25
|
+
- Close event socket during session cleanup by @dieter-medium
|
26
|
+
- Add test for generating PDFs in parallel by @dieter-medium
|
27
|
+
|
28
|
+
### 💄 Style
|
29
|
+
|
30
|
+
- Update Bootstrap stylesheet link by @dieter-medium
|
31
|
+
|
32
|
+
### 🔧 Build
|
33
|
+
|
34
|
+
- Update Ruby version in action.yml by @dieter-medium
|
35
|
+
- Update Dockerfile.chromedriver and chromedriver.yml for enhancements by @dieter-medium
|
36
|
+
- Update Dockerfile.chromedriver for improved environment setup and vnc support by @dieter-medium
|
37
|
+
|
38
|
+
### 🚀 Added
|
39
|
+
|
40
|
+
- Enhance testcontainers with shared network support by @dieter-medium
|
41
|
+
|
14
42
|
## [0.1.8] - 2025-04-22
|
15
43
|
|
16
44
|
### 🎨 Refactored
|
@@ -173,7 +201,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
173
201
|
- Initial release
|
174
202
|
|
175
203
|
|
176
|
-
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.
|
204
|
+
- [unreleased](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.9..HEAD)
|
205
|
+
- [0.1.9](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.8..v0.1.9)
|
177
206
|
- [0.1.8](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.7..v0.1.8)
|
178
207
|
- [0.1.7](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.6..v0.1.7)
|
179
208
|
- [0.1.6](https://github.com/dieter-medium/bidi2pdf/compare/v0.1.5..v0.1.6)
|
@@ -1,13 +1,16 @@
|
|
1
|
-
FROM
|
1
|
+
FROM debian:bookworm-slim
|
2
2
|
|
3
3
|
ARG CHROMEDRIVER_PORT=3000
|
4
4
|
|
5
5
|
ENV DEBIAN_FRONTEND=noninteractive
|
6
|
+
ENV LANG=en_US.UTF-8
|
6
7
|
|
7
8
|
# Install dependencies
|
8
|
-
RUN
|
9
|
+
RUN echo "deb http://deb.debian.org/debian bookworm contrib non-free" > /etc/apt/sources.list.d/contrib.list &&\
|
10
|
+
echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true" | debconf-set-selections &&\
|
11
|
+
apt-get update && apt-get upgrade -y && \
|
9
12
|
apt-get install -y --no-install-recommends\
|
10
|
-
chromium chromium-driver\
|
13
|
+
chromium chromium-driver chromium-l10n chromium-sandbox\
|
11
14
|
libglib2.0-0 \
|
12
15
|
libnss3 \
|
13
16
|
libxss1 \
|
@@ -18,10 +21,21 @@ RUN apt-get update && apt-get upgrade -y && \
|
|
18
21
|
curl \
|
19
22
|
unzip \
|
20
23
|
xvfb \
|
24
|
+
x11vnc \
|
25
|
+
fluxbox \
|
26
|
+
xterm \
|
27
|
+
wmctrl \
|
28
|
+
net-tools xauth \
|
29
|
+
fonts-liberation fonts-dejavu-core fonts-noto-core fonts-noto-cjk fonts-noto-color-emoji fonts-symbola fontconfig ttf-mscorefonts-installer\
|
30
|
+
libnss3 libatk1.0-0 \
|
31
|
+
libx11-6 libxss1 libgtk-3-0 libgbm1 \
|
32
|
+
locales sed \
|
33
|
+
&& sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \
|
34
|
+
&& locale-gen en_US.UTF-8 \
|
21
35
|
&& rm -rf /var/lib/apt/lists/*
|
22
36
|
|
23
37
|
# Create a non-root user
|
24
|
-
RUN groupadd -r appuser && useradd -r -g appuser -m -d /home/appuser appuser
|
38
|
+
RUN groupadd -r appuser && useradd -r -g appuser -G audio,video -m -d /home/appuser appuser
|
25
39
|
|
26
40
|
COPY ./docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
27
41
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
@@ -36,12 +50,16 @@ RUN mkdir -p /home/appuser/.webdrivers && ln -s /usr/bin/chromedriver /home/appu
|
|
36
50
|
# Set working directory
|
37
51
|
WORKDIR /app
|
38
52
|
|
53
|
+
RUN mkdir -p /tmp/.X11-unix && chmod 1777 /tmp/.X11-unix
|
54
|
+
|
39
55
|
# Switch to non-root user
|
40
56
|
USER appuser
|
41
57
|
|
42
|
-
RUN gem install chromedriver-binary && ruby -e 'require "chromedriver/binary"; puts Chromedriver::Binary::ChromedriverDownloader.update'
|
58
|
+
# RUN gem install chromedriver-binary && ruby -e 'require "chromedriver/binary"; puts Chromedriver::Binary::ChromedriverDownloader.update'
|
43
59
|
|
44
60
|
ENV CHROMEDRIVER_PORT=${CHROMEDRIVER_PORT}
|
45
61
|
EXPOSE ${CHROMEDRIVER_PORT}
|
62
|
+
# VNC
|
63
|
+
EXPOSE 5900
|
46
64
|
|
47
65
|
CMD ["/usr/local/bin/entrypoint.sh"]
|
data/docker/entrypoint.sh
CHANGED
@@ -3,6 +3,47 @@
|
|
3
3
|
USER_DATA_DIR=/home/appuser/.cache
|
4
4
|
mkdir -p ${USER_DATA_DIR}
|
5
5
|
|
6
|
+
if [ "$ENABLE_XVFB" = "true" ]; then
|
7
|
+
rm -rf /tmp/.X99-lock
|
8
|
+
|
9
|
+
export DISPLAY=:99
|
10
|
+
Xvfb :99 -screen 0 1920x1080x24 &
|
11
|
+
|
12
|
+
old_umask=$(umask)
|
13
|
+
umask 077
|
14
|
+
|
15
|
+
touch /home/appuser/.Xauthority
|
16
|
+
export XAUTHORITY=/home/appuser/.Xauthority
|
17
|
+
|
18
|
+
xauth generate :99 . trusted
|
19
|
+
|
20
|
+
umask $old_umask
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
until xdpyinfo -display ${DISPLAY} >/dev/null 2>&1; do
|
25
|
+
sleep 0.2
|
26
|
+
done
|
27
|
+
|
28
|
+
fluxbox &
|
29
|
+
|
30
|
+
until wmctrl -m > /dev/null 2>&1; do
|
31
|
+
sleep 0.2
|
32
|
+
done
|
33
|
+
fi
|
34
|
+
|
35
|
+
if [ "$ENABLE_VNC" = "true" ]; then
|
36
|
+
VNC_PASS=${VNC_PASS:-$(tr -dc A-Za-z0-9 </dev/urandom | head -c 12)}
|
37
|
+
echo "VNC password: $VNC_PASS"
|
38
|
+
old_umask=$(umask)
|
39
|
+
umask 077
|
40
|
+
mkdir -p /home/appuser/.vnc
|
41
|
+
x11vnc -storepasswd $VNC_PASS /home/appuser/.vnc/passwd
|
42
|
+
umask $old_umask
|
43
|
+
x11vnc -display WAIT:99 -xkb -noxrecord -noxfixes -noxdamage -forever -shared -noshm -usepw -rfbauth /home/appuser/.vnc/passwd &
|
44
|
+
fi
|
45
|
+
|
46
|
+
# DISPLAY=:99 /home/appuser/.webdrivers/chromedriver --port=33259 --whitelisted-ips="" --allowed-origins="*" --disable-dev-shm-usage --disable-gpu --verbose
|
6
47
|
/home/appuser/.webdrivers/chromedriver --port=${CHROMEDRIVER_PORT} \
|
7
48
|
--headless \
|
8
49
|
--whitelisted-ips="" \
|
@@ -141,7 +141,7 @@ module Bidi2pdf
|
|
141
141
|
headers:,
|
142
142
|
url_patterns:
|
143
143
|
)
|
144
|
-
AddHeadersInterceptor.new(
|
144
|
+
@header_interceptor = AddHeadersInterceptor.new(
|
145
145
|
context: browsing_context_id,
|
146
146
|
url_patterns: url_patterns,
|
147
147
|
headers: headers
|
@@ -155,7 +155,7 @@ module Bidi2pdf
|
|
155
155
|
# @param [Array<String>] url_patterns The URL patterns to match.
|
156
156
|
# @return [AuthInterceptor] The interceptor instance.
|
157
157
|
def basic_auth(username:, password:, url_patterns:)
|
158
|
-
AuthInterceptor.new(
|
158
|
+
@basic_auth_interceptor = AuthInterceptor.new(
|
159
159
|
context: browsing_context_id,
|
160
160
|
url_patterns: url_patterns,
|
161
161
|
username: username, password: password
|
@@ -430,14 +430,14 @@ module Bidi2pdf
|
|
430
430
|
return if @event_handlers_registered
|
431
431
|
|
432
432
|
@event_handlers_registered = true
|
433
|
+
@listener_refs ||= {}
|
433
434
|
|
434
|
-
client.on_event("network.beforeRequestSent", "network.responseStarted", "network.responseCompleted", "network.fetchError",
|
435
|
-
|
435
|
+
@listener_refs[:network] = client.on_event("network.beforeRequestSent", "network.responseStarted", "network.responseCompleted", "network.fetchError",
|
436
|
+
&network_events.method(:handle_event))
|
436
437
|
|
437
|
-
client.on_event("log.entryAdded",
|
438
|
-
&logger_events.method(:handle_event))
|
438
|
+
@listener_refs[:logger] = client.on_event("log.entryAdded", &logger_events.method(:handle_event))
|
439
439
|
|
440
|
-
client.on_event("browsingContext.navigationFailed", &navigation_failed_events.method(:handle_event))
|
440
|
+
@listener_refs[:navigation_failed] = client.on_event("browsingContext.navigationFailed", &navigation_failed_events.method(:handle_event))
|
441
441
|
end
|
442
442
|
|
443
443
|
def handle_injection_exception(response, url, exception_class)
|
@@ -568,16 +568,27 @@ module Bidi2pdf
|
|
568
568
|
end
|
569
569
|
|
570
570
|
# Removes event listeners for the browser tab.
|
571
|
+
# rubocop:disable Metrics/AbcSize
|
571
572
|
def remove_event_listeners
|
573
|
+
return if @listener_refs.nil? || @listener_refs.empty?
|
574
|
+
|
572
575
|
Bidi2pdf.logger.debug2 "Network events: #{network_events.all_events.map(&:to_s)}"
|
573
576
|
|
574
|
-
client.remove_event_listener "network.responseStarted", "network.responseCompleted", "network.fetchError",
|
575
|
-
|
577
|
+
client.remove_event_listener("network.beforeRequestSent", "network.responseStarted", "network.responseCompleted", "network.fetchError",
|
578
|
+
@listener_refs[:network])
|
579
|
+
|
580
|
+
client.remove_event_listener("log.entryAdded", @listener_refs[:logger])
|
576
581
|
|
577
|
-
client
|
578
|
-
|
582
|
+
@header_interceptor&.unregister_with_client(client: client)
|
583
|
+
@basic_auth_interceptor&.unregister_with_client(client: client)
|
584
|
+
|
585
|
+
@header_interceptor = nil
|
586
|
+
@basic_auth_interceptor = nil
|
587
|
+
@listener_refs = {}
|
579
588
|
end
|
580
589
|
|
590
|
+
# rubocop:enable Metrics/AbcSize
|
591
|
+
|
581
592
|
# Closes all tabs associated with the browser tab.
|
582
593
|
def close_tabs
|
583
594
|
tabs.each do |tab|
|
data/lib/bidi2pdf/bidi/client.rb
CHANGED
@@ -125,23 +125,25 @@ module Bidi2pdf
|
|
125
125
|
#
|
126
126
|
# @param [Array<String>] names The names of the events to subscribe to.
|
127
127
|
# @yield [event_data] A block to handle the event data.
|
128
|
-
def on_event(*names, &
|
129
|
-
|
128
|
+
def on_event(*names, &)
|
129
|
+
listener = dispatcher.on_event(*names, &)
|
130
130
|
cmd = Bidi2pdf::Bidi::Commands::SessionSubscribe.new(events: names)
|
131
131
|
send_cmd(cmd) if names.any?
|
132
|
+
|
133
|
+
listener
|
132
134
|
end
|
133
135
|
|
134
136
|
# Removes a message listener.
|
135
137
|
#
|
136
138
|
# @param [Proc] block The listener block to remove.
|
137
|
-
def remove_message_listener(
|
139
|
+
def remove_message_listener(listener) = dispatcher.remove_message_listener(listener)
|
138
140
|
|
139
141
|
# Removes event listeners for specific events.
|
140
142
|
#
|
141
143
|
# @param [Array<String>] names The names of the events to unsubscribe from.
|
142
144
|
# @param [Proc] block The listener block to remove.
|
143
|
-
def remove_event_listener(*names,
|
144
|
-
names.each { |event_name| dispatcher.remove_event_listener(event_name,
|
145
|
+
def remove_event_listener(*names, listener)
|
146
|
+
names.each { |event_name| dispatcher.remove_event_listener(event_name, listener) }
|
145
147
|
end
|
146
148
|
|
147
149
|
# Closes the WebSocket connection.
|
@@ -3,6 +3,27 @@
|
|
3
3
|
module Bidi2pdf
|
4
4
|
module Bidi
|
5
5
|
class EventManager
|
6
|
+
Listener = Struct.new(:block, :id, :source_location) do
|
7
|
+
def initialize(block, id = SecureRandom.uuid)
|
8
|
+
super
|
9
|
+
self.source_location = block.source_location
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(*args)
|
13
|
+
block.call(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(other)
|
17
|
+
other.is_a?(Listener) && id == other.id
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :eql?, :==
|
21
|
+
|
22
|
+
def hash
|
23
|
+
id.hash
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
6
27
|
attr_reader :type
|
7
28
|
|
8
29
|
def initialize(type)
|
@@ -11,12 +32,21 @@ module Bidi2pdf
|
|
11
32
|
end
|
12
33
|
|
13
34
|
def on(*event_names, &block)
|
14
|
-
|
15
|
-
|
16
|
-
|
35
|
+
Listener.new(block).tap do |listener|
|
36
|
+
event_names.each do |event_name|
|
37
|
+
@listeners[event_name.to_sym] << listener
|
38
|
+
log_msg("Adding #{event_name} listener", listener)
|
39
|
+
end
|
40
|
+
end
|
17
41
|
end
|
18
42
|
|
19
|
-
def off(event_name,
|
43
|
+
def off(event_name, listener)
|
44
|
+
raise ArgumentError, "Listener not registered" unless listener.is_a?(Listener)
|
45
|
+
|
46
|
+
log_msg("Removing #{event_name} listener", listener)
|
47
|
+
|
48
|
+
@listeners[event_name.to_sym].delete(listener)
|
49
|
+
end
|
20
50
|
|
21
51
|
def dispatch(event_name, *args)
|
22
52
|
listeners = @listeners[event_name.to_sym] || []
|
@@ -27,14 +27,24 @@ module Bidi2pdf
|
|
27
27
|
client.send_cmd_and_wait(cmd) do |response|
|
28
28
|
@interceptor_id = response["result"]["intercept"]
|
29
29
|
|
30
|
-
Bidi2pdf.logger.
|
30
|
+
Bidi2pdf.logger.debug2 "Interceptor added: #{@interceptor_id}"
|
31
31
|
|
32
|
-
client.on_event(*self.class.events, &method(:handle_event))
|
32
|
+
@handle_event_listener = client.on_event(*self.class.events, &method(:handle_event))
|
33
33
|
|
34
34
|
self
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
def unregister_with_client(client:)
|
39
|
+
return unless @handle_event_listener
|
40
|
+
|
41
|
+
client.remove_event_listener(*self.class.events, @handle_event_listener)
|
42
|
+
|
43
|
+
Bidi2pdf.logger.debug2 "Interceptor removed: #{@interceptor_id}"
|
44
|
+
|
45
|
+
@handle_event_listener = nil
|
46
|
+
end
|
47
|
+
|
38
48
|
# rubocop: disable Metrics/AbcSize
|
39
49
|
def handle_event(response)
|
40
50
|
event_response = response["params"]
|
@@ -165,6 +165,7 @@ module Bidi2pdf
|
|
165
165
|
Bidi2pdf.logger.info "Subscribing to events"
|
166
166
|
|
167
167
|
Bidi::Client.new(websocket_url).tap do |event_client|
|
168
|
+
@event_socket = event_client
|
168
169
|
event_client.start
|
169
170
|
event_client.wait_until_open
|
170
171
|
|
@@ -320,6 +321,7 @@ module Bidi2pdf
|
|
320
321
|
# Cleans up resources associated with the session.
|
321
322
|
def cleanup
|
322
323
|
@client&.close
|
324
|
+
@event_socket&.close
|
323
325
|
@client = @websocket_url = @browser = nil
|
324
326
|
end
|
325
327
|
end
|
@@ -24,7 +24,7 @@ module Bidi2pdf
|
|
24
24
|
|
25
25
|
def on_message(&) = socket_events.on(:message, &)
|
26
26
|
|
27
|
-
def on_event(
|
27
|
+
def on_event(*event_names, &) = session_events.on(*event_names, &)
|
28
28
|
|
29
29
|
def on_open(&) = socket_events.on(:open, &)
|
30
30
|
|
@@ -34,13 +34,13 @@ module Bidi2pdf
|
|
34
34
|
|
35
35
|
def remove_message_listener(block) = socket_events.off(:message, block)
|
36
36
|
|
37
|
-
def remove_event_listener(name,
|
37
|
+
def remove_event_listener(name, listener) = session_events.off(name, listener)
|
38
38
|
|
39
|
-
def remove_open_listener(
|
39
|
+
def remove_open_listener(listener) = socket_events.off(:open, listener)
|
40
40
|
|
41
|
-
def remove_close_listener(
|
41
|
+
def remove_close_listener(listener) = socket_events.off(:close, listener)
|
42
42
|
|
43
|
-
def remove_error_listener(
|
43
|
+
def remove_error_listener(listener) = socket_events.off(:error, listener)
|
44
44
|
|
45
45
|
private
|
46
46
|
|
@@ -8,6 +8,7 @@ module Bidi2pdf
|
|
8
8
|
include Chromedriver::Binary::Platform
|
9
9
|
|
10
10
|
attr_reader :port, :pid, :started, :headless, :chrome_args, :shutdown_mutex
|
11
|
+
attr_accessor :reader_thread
|
11
12
|
|
12
13
|
def initialize(port: 0, headless: true, chrome_args: Bidi::Session::DEFAULT_CHROME_ARGS)
|
13
14
|
@port = port
|
@@ -49,10 +50,20 @@ module Bidi2pdf
|
|
49
50
|
"http://localhost:#{@port}/session"
|
50
51
|
end
|
51
52
|
|
53
|
+
# rubocop: disable Metrics/AbcSize
|
52
54
|
def stop(timeout: 5)
|
53
55
|
shutdown_mutex.synchronize do
|
54
56
|
return unless @pid
|
55
57
|
|
58
|
+
if reader_thread&.alive?
|
59
|
+
begin
|
60
|
+
reader_thread.kill
|
61
|
+
reader_thread.join
|
62
|
+
rescue StandardError => e
|
63
|
+
Bidi2pdf.logger.error "Error killing reader thread: #{e.message}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
56
67
|
@started = false
|
57
68
|
|
58
69
|
close_session
|
@@ -72,6 +83,8 @@ module Bidi2pdf
|
|
72
83
|
end
|
73
84
|
end
|
74
85
|
|
86
|
+
# rubocop: enable Metrics/AbcSize
|
87
|
+
|
75
88
|
private
|
76
89
|
|
77
90
|
def spawn_process(cmd)
|
@@ -184,25 +197,26 @@ module Bidi2pdf
|
|
184
197
|
|
185
198
|
# rubocop: disable Metrics/AbcSize
|
186
199
|
def parse_port_from_output(io, timeout: 5)
|
187
|
-
|
188
|
-
io.each_line do |line|
|
189
|
-
Bidi2pdf.logger.debug1 line.chomp
|
200
|
+
port_event = Concurrent::Event.new
|
190
201
|
|
191
|
-
|
192
|
-
|
193
|
-
Bidi2pdf.logger.
|
194
|
-
|
195
|
-
@port = ::Regexp.last_match(1).to_i if @port.nil? || @port.zero?
|
202
|
+
self.reader_thread = Thread.new do
|
203
|
+
io.each_line do |line|
|
204
|
+
Bidi2pdf.logger.info "[chromedriver] #{line.chomp}"
|
196
205
|
|
197
|
-
|
206
|
+
if line =~ /ChromeDriver was started successfully on port (\d+)/
|
207
|
+
@port = ::Regexp.last_match(1).to_i if @port.nil? || @port.zero?
|
208
|
+
port_event.set
|
209
|
+
end
|
198
210
|
end
|
199
211
|
rescue IOError
|
200
212
|
# reader closed
|
201
213
|
ensure
|
202
214
|
io.close unless io.closed?
|
203
|
-
end
|
215
|
+
end
|
216
|
+
|
217
|
+
return if port_event.wait(timeout)
|
204
218
|
|
205
|
-
raise "Chromedriver did not report a usable port in #{timeout}s"
|
219
|
+
raise "Chromedriver did not report a usable port in #{timeout}s"
|
206
220
|
end
|
207
221
|
|
208
222
|
# rubocop: enable Metrics/AbcSize
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "shared_docker_network"
|
4
|
+
|
5
|
+
module Bidi2pdf
|
6
|
+
module TestHelpers
|
7
|
+
module Testcontainers
|
8
|
+
module ChromedriverTestHelper
|
9
|
+
def session_url
|
10
|
+
chromedriver_container.session_url
|
11
|
+
end
|
12
|
+
|
13
|
+
def chromedriver_container
|
14
|
+
RSpec.configuration.chromedriver_container
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module SessionTestHelper
|
19
|
+
def chrome_args
|
20
|
+
chrome_args = Bidi2pdf::Bidi::Session::DEFAULT_CHROME_ARGS.dup
|
21
|
+
|
22
|
+
# within github actions, the sandbox is not supported, when we start our own container
|
23
|
+
# some privileges are not available ???
|
24
|
+
if ENV["DISABLE_CHROME_SANDBOX"]
|
25
|
+
chrome_args << "--no-sandbox"
|
26
|
+
|
27
|
+
puts "🚨 Chrome sandbox disabled"
|
28
|
+
end
|
29
|
+
chrome_args
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_session(session_url)
|
33
|
+
Bidi2pdf::Bidi::Session.new(session_url: session_url, headless: true, chrome_args: chrome_args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
RSpec.configure do |config|
|
41
|
+
config.add_setting :chromedriver_container, default: nil
|
42
|
+
|
43
|
+
config.include Bidi2pdf::TestHelpers::Testcontainers::ChromedriverTestHelper, chromedriver: true
|
44
|
+
config.include Bidi2pdf::TestHelpers::Testcontainers::SessionTestHelper, session: true
|
45
|
+
|
46
|
+
config.before(:suite) do
|
47
|
+
if chromedriver_tests_present?
|
48
|
+
config.chromedriver_container = start_chromedriver_container(
|
49
|
+
build_dir: File.join(config.docker_dir, ".."),
|
50
|
+
mounts: config.respond_to?(:chromedriver_mounts) ? config.chromedriver_mounts : {},
|
51
|
+
shared_network: config.shared_network
|
52
|
+
)
|
53
|
+
|
54
|
+
puts "🚀 chromedriver container started for tests"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
config.after(:suite) do
|
59
|
+
stop_container config.chromedriver_container
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def stop_container(container)
|
64
|
+
if container&.running?
|
65
|
+
|
66
|
+
if ENV["SHOW_CONTAINER_LOGS"]
|
67
|
+
puts "Container logs:"
|
68
|
+
logs_std, logs_error = container.logs
|
69
|
+
|
70
|
+
puts logs_error
|
71
|
+
puts logs_std
|
72
|
+
end
|
73
|
+
|
74
|
+
puts "🧹 #{container.image} stopping container..."
|
75
|
+
container.stop
|
76
|
+
end
|
77
|
+
container&.remove
|
78
|
+
end
|
79
|
+
|
80
|
+
def chromedriver_tests_present?
|
81
|
+
test_of_kind_present? :chromedriver
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_of_kind_present?(type)
|
85
|
+
RSpec.world.filtered_examples.values.flatten.any? { |example| example.metadata[type] }
|
86
|
+
end
|
87
|
+
|
88
|
+
# alias the long class name
|
89
|
+
ChromedriverTestcontainer = Bidi2pdf::TestHelpers::Testcontainers::ChromedriverContainer
|
90
|
+
|
91
|
+
def start_chromedriver_container(build_dir:, mounts:, shared_network:)
|
92
|
+
container = ChromedriverTestcontainer.new(ChromedriverTestcontainer::DEFAULT_IMAGE,
|
93
|
+
build_dir: build_dir,
|
94
|
+
docker_file: "docker/Dockerfile.chromedriver")
|
95
|
+
.with_network(shared_network)
|
96
|
+
.with_network_aliases("remote-chrome")
|
97
|
+
|
98
|
+
container.with_filesystem_binds(mounts) if mounts&.any?
|
99
|
+
|
100
|
+
container.start
|
101
|
+
|
102
|
+
container
|
103
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
config.add_setting :shared_network, default: nil
|
5
|
+
|
6
|
+
config.before(:suite) do
|
7
|
+
examples = RSpec.world.filtered_examples.values.flatten
|
8
|
+
uses_containers = examples.any? do |ex|
|
9
|
+
ex.metadata[:nginx] || ex.metadata[:chrome] || ex.metadata[:chromedriver] || ex.metadata[:container]
|
10
|
+
end
|
11
|
+
|
12
|
+
if uses_containers
|
13
|
+
config.shared_network = Docker::Network.create("bidi2pdf-test-net-#{SecureRandom.hex(4)}")
|
14
|
+
puts "🕸️ started shared network #{config.shared_network}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
config.after(:suite) do
|
19
|
+
config.shared_network&.remove
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bidi2pdf
|
4
|
+
module TestHelpers
|
5
|
+
module TestcontainersRefinement
|
6
|
+
def id
|
7
|
+
@_id
|
8
|
+
end
|
9
|
+
|
10
|
+
def aliases
|
11
|
+
@aliases ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def aliases=(aliases)
|
15
|
+
@aliases = aliases
|
16
|
+
end
|
17
|
+
|
18
|
+
def network
|
19
|
+
@_network
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_network(network)
|
23
|
+
@_network = network
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def with_network_aliases(*aliases)
|
28
|
+
self.aliases += aliases
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def _container_create_options
|
33
|
+
opts = super
|
34
|
+
network_name = network ? network.info["Name"] : nil
|
35
|
+
opts["HostConfig"]["NetworkMode"] = network_name
|
36
|
+
|
37
|
+
if network && aliases.any?
|
38
|
+
opts["NetworkingConfig"] = {
|
39
|
+
"EndpointsConfig" => {
|
40
|
+
network_name => {
|
41
|
+
"Aliases" => aliases
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.compact
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
Testcontainers::DockerContainer.prepend(Bidi2pdf::TestHelpers::TestcontainersRefinement)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
%w[docker testcontainers].each do |dep|
|
4
|
+
require dep
|
5
|
+
rescue LoadError
|
6
|
+
warn "Missing #{dep}. Add it to your Gemfile if you're using Bidi2pdf test helpers."
|
7
|
+
end
|
8
|
+
|
9
|
+
module Bidi2pdf
|
10
|
+
module TestHelpers
|
11
|
+
module Testcontainers
|
12
|
+
require_relative "testcontainers/testcontainers_refinement"
|
13
|
+
require_relative "testcontainers/chromedriver_container"
|
14
|
+
require_relative "testcontainers/chromedriver_test_helper"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/bidi2pdf/version.rb
CHANGED
@@ -1,29 +1,35 @@
|
|
1
1
|
module Bidi2pdf
|
2
2
|
module Bidi
|
3
3
|
class EventManager
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
class Listener
|
5
|
+
attr_reader block: untyped
|
6
|
+
attr_reader id: String
|
7
7
|
|
8
|
-
|
8
|
+
def initialize: (untyped block, ?String id) -> void
|
9
9
|
|
10
|
-
|
10
|
+
def call: (*untyped args) -> untyped
|
11
11
|
|
12
|
-
|
12
|
+
def ==: (untyped other) -> bool
|
13
13
|
|
14
|
-
|
14
|
+
def eql?: (untyped other) -> bool
|
15
15
|
|
16
|
-
|
16
|
+
def hash: () -> Integer
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
+
@listeners: untyped
|
20
|
+
@type: untyped
|
19
21
|
|
20
|
-
|
22
|
+
attr_reader type: untyped
|
21
23
|
|
22
|
-
def
|
24
|
+
def initialize: (untyped type) -> void
|
23
25
|
|
24
|
-
def
|
26
|
+
def on: (*untyped event_names, &untyped block) -> Listener
|
25
27
|
|
26
|
-
def
|
28
|
+
def off: (untyped event_name, Listener listener) -> void
|
29
|
+
|
30
|
+
def dispatch: (untyped event_name, *untyped args) -> void
|
31
|
+
|
32
|
+
def clear: (?untyped event_name) -> void
|
27
33
|
end
|
28
34
|
end
|
29
35
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bidi2pdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dieter S.
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: base64
|
@@ -425,7 +424,11 @@ files:
|
|
425
424
|
- lib/bidi2pdf/test_helpers/matchers/match_pdf_text.rb
|
426
425
|
- lib/bidi2pdf/test_helpers/pdf_reader_utils.rb
|
427
426
|
- lib/bidi2pdf/test_helpers/pdf_text_sanitizer.rb
|
427
|
+
- lib/bidi2pdf/test_helpers/testcontainers.rb
|
428
428
|
- lib/bidi2pdf/test_helpers/testcontainers/chromedriver_container.rb
|
429
|
+
- lib/bidi2pdf/test_helpers/testcontainers/chromedriver_test_helper.rb
|
430
|
+
- lib/bidi2pdf/test_helpers/testcontainers/shared_docker_network.rb
|
431
|
+
- lib/bidi2pdf/test_helpers/testcontainers/testcontainers_refinement.rb
|
429
432
|
- lib/bidi2pdf/verbose_logger.rb
|
430
433
|
- lib/bidi2pdf/version.rb
|
431
434
|
- sig/bidi2pdf.rbs
|
@@ -484,7 +487,6 @@ metadata:
|
|
484
487
|
source_code_uri: https://github.com/dieter-medium/bidi2pdf
|
485
488
|
changelog_uri: https://github.com/dieter-medium/bidi2pdf/blob/master/CHANGELOG.md
|
486
489
|
rubygems_mfa_required: 'true'
|
487
|
-
post_install_message:
|
488
490
|
rdoc_options: []
|
489
491
|
require_paths:
|
490
492
|
- lib
|
@@ -499,8 +501,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
499
501
|
- !ruby/object:Gem::Version
|
500
502
|
version: '0'
|
501
503
|
requirements: []
|
502
|
-
rubygems_version: 3.
|
503
|
-
signing_key:
|
504
|
+
rubygems_version: 3.6.8
|
504
505
|
specification_version: 4
|
505
506
|
summary: A Ruby gem that generates PDFs from web pages using Chrome's BiDi protocol,
|
506
507
|
providing high-quality PDF documents from any URL with full support for modern web
|