async-webdriver 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: feeb6491f2ca71f3545e538223b1913f582f361e3f60904121be38a0c3336472
4
- data.tar.gz: 13738ddaaa84a0f1a1bab59194f31445035c3e12939ee815e7a28947f22bc093
3
+ metadata.gz: da08a7e4f4e91c945652030f4616b76e9b087cc84efb98f6ee48c99504407abc
4
+ data.tar.gz: bf78251edf46c21d60b73adfa60b3105a9c114a9c0de26454ca59ed1cba85198
5
5
  SHA512:
6
- metadata.gz: 2b995fa91a0df9b3ee5b921b3230ba216a671133b0832494d7696e3ad70df59dc297fedbb7fdd2f8f82ab70b1b9eb8aeab5c5464370828817c6e14db3bf8e364
7
- data.tar.gz: 0b631916ad896c782e6f1268d93b11c9d2ef741b49aaf7b487c3bdbf2aed5d9b2e094dcd3a581e18d761bf366895ce48c4c8dfb304a2eb0d9f500d0cd274e7ed
6
+ metadata.gz: 79921aa72ebc306b70a5615de0aec9b2ffa5e6c56186f08ce781b69837f71911334512148678320da898dcf546f310de9157e554ee65a00bd37b05f6584f6303
7
+ data.tar.gz: 7f193306b1552c179a96b8d2ca832b979d6118bea9c8c9a22da5550e454de1b3282605486191727ed5af7cd3eb674c0b231bb8aad82b84f7ad353c08f5c23adf
checksums.yaml.gz.sig CHANGED
@@ -1,3 +1,3 @@
1
- rX<)`��5]����)'pޢ���@�7��V)ԀR]Q� %PM
2
- ����,�O���&n h���Y
3
- ��lk%�bcUcKL�&rv]��m�;b�x��/5XT7u�|F���,��ֽ; ��L��qH�#-Q�i�<� ��\N퉞� ' �vH����*n{��V?]b*��Ce�u��$�D@".�$��lm�{� �-FP<�xH5GOo��ilGp��;X�����/V�-ы��3#��^-���M�.'��Ry��UO�#v�D{ҡH��\����7��)�s�pw��3��]�&�T�p�,r�\58�vZ��
1
+ -1
2
+ �}@���������)�r��� 5�B�j_|]W��@@:�*��o�Os�$��SuP��c k���������V� �O�,�ć�h�hJH�N}��]�#�>�
3
+ f�wW��9c0f?�M��f������,pZѢ!��t���CXN �\P$�K�`੓���J�i.#���o=�h{���6{k�g2d��G��Oڹ�):�!skHL�*d#«�Jf����?��U�h��R���J$��$�����3| wҡ���"姴��{C��e[~8��s��x��e@�rb��� .sW�Ɠ�q}���gw� ��=7�Ҟ��V nm��h
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'generic'
7
7
  require_relative 'process_group'
@@ -20,58 +20,65 @@ module Async
20
20
  # end
21
21
  # ```
22
22
  class Chrome < Generic
23
- # Create a new bridge to Chrome.
24
- # @parameter path [String] The path to the `chromedriver` executable.
25
- def initialize(path: "chromedriver")
26
- super()
27
-
28
- @path = path
29
- @process = nil
23
+ def path
24
+ @options.fetch(:path, "chromedriver")
30
25
  end
31
26
 
32
27
  # @returns [String] The version of the `chromedriver` executable.
33
28
  def version
34
- ::IO.popen([@path, "--version"]) do |io|
29
+ ::IO.popen([self.path, "--version"]) do |io|
35
30
  return io.read
36
31
  end
37
32
  rescue Errno::ENOENT
38
33
  return nil
39
34
  end
40
35
 
41
- # @returns [Array(String)] The arguments to pass to the `chromedriver` executable.
42
- def arguments
43
- [
44
- "--port=#{self.port}",
45
- ].compact
46
- end
47
-
48
- # Start the driver.
49
- def start
50
- @process ||= ProcessGroup.spawn(@path, *arguments)
36
+ class Driver < Bridge::Driver
37
+ def initialize(**options)
38
+ super(**options)
39
+ @process_group = nil
40
+ end
51
41
 
52
- super
53
- end
54
-
55
- # Close the driver.
56
- def close
57
- super
42
+ # @returns [Array(String)] The arguments to pass to the `chromedriver` executable.
43
+ def arguments(**options)
44
+ [
45
+ options.fetch(:path, "chromedriver"),
46
+ "--port=#{self.port}",
47
+ ].compact
48
+ end
49
+
50
+ def start
51
+ @process_group = ProcessGroup.spawn(*arguments(**@options))
52
+
53
+ super
54
+ end
58
55
 
59
- if @process
60
- @process.close
61
- @process = nil
56
+ def close
57
+ if @process_group
58
+ @process_group.close
59
+ @process_group = nil
60
+ end
61
+
62
+ super
62
63
  end
63
64
  end
64
65
 
66
+ # Start the driver.
67
+ def start(**options)
68
+ Driver.new(**options).tap(&:start)
69
+ end
70
+
65
71
  # The default capabilities for the Chrome browser which need to be provided when requesting a new session.
66
72
  # @parameter headless [Boolean] Whether to run the browser in headless mode.
67
73
  # @returns [Hash] The default capabilities for the Chrome browser.
68
- def default_capabilities(headless: true)
74
+ def default_capabilities(headless: self.headless?)
69
75
  {
70
76
  alwaysMatch: {
71
77
  browserName: "chrome",
72
78
  "goog:chromeOptions": {
73
79
  args: [headless ? "--headless" : nil].compact,
74
- }
80
+ },
81
+ webSocketUrl: true,
75
82
  },
76
83
  }
77
84
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ module Async
7
+ module WebDriver
8
+ module Bridge
9
+ # Represents an instance of a locally running driver (usually with a process group).
10
+ class Driver
11
+ def initialize(**options)
12
+ @options = options
13
+ @count = 0
14
+ @closed = false
15
+ end
16
+
17
+ def concurrency
18
+ @options.fetch(:concurrency, 128)
19
+ end
20
+
21
+ attr :count
22
+
23
+ # @attribute [Hash] The status of the driver after a connection has been established.
24
+ attr :status
25
+
26
+ def viable?
27
+ !@closed
28
+ end
29
+
30
+ def closed?
31
+ @closed
32
+ end
33
+
34
+ def close
35
+ @closed = true
36
+ end
37
+
38
+ def reusable?
39
+ @options.fetch(:reusable, !@closed)
40
+ end
41
+
42
+ # Generate a port number for the driver to listen on if it was not specified.
43
+ # @returns [Integer] An ephemeral port number.
44
+ def ephemeral_port
45
+ address = ::Addrinfo.tcp("localhost", 0)
46
+
47
+ address.bind do |socket|
48
+ # We assume that it's unlikely the port will be reused any time soon...
49
+ return socket.local_address.ip_port
50
+ end
51
+ end
52
+
53
+ def port
54
+ @port ||= @options.fetch(:port, self.ephemeral_port)
55
+ end
56
+
57
+ def endpoint
58
+ Async::HTTP::Endpoint.parse("http://localhost", port: self.port)
59
+ end
60
+
61
+ def client
62
+ Client.open(self.endpoint)
63
+ end
64
+
65
+ # Start the driver.
66
+ # @parameter retries [Integer] The number of times to retry before giving up.
67
+ def start(retries: 100)
68
+ endpoint = self.endpoint
69
+
70
+ Console.debug(self, "Waiting for driver to start...", endpoint: endpoint)
71
+ count = 0
72
+
73
+ Async::HTTP::Client.open(endpoint) do |client|
74
+ begin
75
+ response = client.get("/status")
76
+ @status = JSON.parse(response.read)["value"]
77
+ Console.debug(self, "Successfully connected to driver.", status: @status)
78
+ rescue Errno::ECONNREFUSED
79
+ if count < retries
80
+ count += 1
81
+ sleep(0.01 * count)
82
+ Console.debug(self, "Driver not ready, retrying...")
83
+ retry
84
+ else
85
+ raise
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'generic'
7
7
  require_relative 'process_group'
@@ -19,57 +19,67 @@ module Async
19
19
  # bridge&.close
20
20
  # end
21
21
  class Firefox < Generic
22
- # Create a new bridge to Firefox.
23
- # @parameter path [String] The path to the `geckodriver` executable.
24
- def initialize(path: "geckodriver")
25
- super()
26
-
27
- @path = path
28
- @process = nil
22
+ def path
23
+ @options.fetch(:path, "geckodriver")
29
24
  end
30
25
 
31
26
  # @returns [String] The version of the `geckodriver` executable.
32
27
  def version
33
- ::IO.popen([@path, "--version"]) do |io|
28
+ ::IO.popen([self.path, "--version"]) do |io|
34
29
  return io.read
35
30
  end
36
31
  rescue Errno::ENOENT
37
32
  return nil
38
33
  end
39
34
 
40
- # @returns [Array(String)] The arguments to pass to the `geckodriver` executable.
41
- def arguments
42
- [
43
- "--port", self.port.to_s,
44
- ].compact
45
- end
46
-
47
- # Start the driver.
48
- def start
49
- @process ||= ProcessGroup.spawn(@path, *arguments)
35
+ class Driver < Bridge::Driver
36
+ def initialize(**options)
37
+ super(**options)
38
+ @process_group = nil
39
+ end
50
40
 
51
- super
52
- end
53
-
54
- # Close the driver.
55
- def close
56
- super
41
+ def concurrency
42
+ 1
43
+ end
57
44
 
58
- if @process
59
- @process.close
60
- @process = nil
45
+ # @returns [Array(String)] The arguments to pass to the `chromedriver` executable.
46
+ def arguments(**options)
47
+ [
48
+ options.fetch(:path, "geckodriver"),
49
+ "--port", self.port.to_s,
50
+ ].compact
61
51
  end
52
+
53
+ def start
54
+ @process_group = ProcessGroup.spawn(*arguments(**@options))
55
+
56
+ super
57
+ end
58
+
59
+ def close
60
+ if @process_group
61
+ @process_group.close
62
+ @process_group = nil
63
+ end
64
+
65
+ super
66
+ end
67
+ end
68
+
69
+ # Start the driver.
70
+ def start(**options)
71
+ Driver.new(**options).tap(&:start)
62
72
  end
63
73
 
64
74
  # The default capabilities for the Firefox browser which need to be provided when requesting a new session.
65
75
  # @parameter headless [Boolean] Whether to run the browser in headless mode.
66
76
  # @returns [Hash] The default capabilities for the Firefox browser.
67
- def default_capabilities(headless: true)
77
+ def default_capabilities(headless: self.headless?)
68
78
  {
69
79
  alwaysMatch: {
70
80
  browserName: "firefox",
71
81
  "moz:firefoxOptions": {
72
- "args": [headless ? "-headless" : nil].compact,
82
+ args: [headless ? "-headless" : nil].compact,
73
83
  }
74
84
  }
75
85
  }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
6
  require 'socket'
7
7
  require 'async/http/endpoint'
@@ -12,23 +12,14 @@ module Async
12
12
  module Bridge
13
13
  # Generic W3C WebDriver implementation.
14
14
  class Generic
15
- # Start the driver and return a new instance.
16
15
  def self.start(**options)
17
- self.new(**options).tap do |bridge|
18
- bridge.start
19
- end
16
+ self.new(**options).start
20
17
  end
21
18
 
22
- # Initialize the driver.
23
- # @parameter port [Integer] The port to listen on.
24
- def initialize(port: nil)
25
- @port = port
26
- @status = nil
19
+ def initialize(**options)
20
+ @options = options
27
21
  end
28
22
 
29
- # @attribute [String] The status of the driver after a connection has been established.
30
- attr :status
31
-
32
23
  # @returns [String | Nil] The version of the driver.
33
24
  def version
34
25
  nil
@@ -39,51 +30,8 @@ module Async
39
30
  version != nil
40
31
  end
41
32
 
42
- # Start the driver.
43
- # @parameter retries [Integer] The number of times to retry before giving up.
44
- def start(retries: 100)
45
- Console.debug(self, "Waiting for driver to start...")
46
- count = 0
47
-
48
- Async::HTTP::Client.open(endpoint) do |client|
49
- begin
50
- response = client.get("/status")
51
- @status = JSON.parse(response.read)["value"]
52
- Console.debug(self, "Successfully connected to driver.", status: @status)
53
- rescue Errno::ECONNREFUSED
54
- if count < retries
55
- count += 1
56
- sleep(0.001 * count)
57
- Console.debug(self, "Driver not ready, retrying...")
58
- retry
59
- else
60
- raise
61
- end
62
- end
63
- end
64
- end
65
-
66
- # Close the driver and any associated resources.
67
- def close
68
- end
69
-
70
- # Generate a port number for the driver to listen on if it was not specified.
71
- # @returns [Integer] The port the driver is listening on.
72
- def port
73
- unless @port
74
- address = ::Addrinfo.tcp("localhost", 0)
75
- address.bind do |socket|
76
- # We assume that it's unlikely the port will be reused any time soon...
77
- @port = socket.local_address.ip_port
78
- end
79
- end
80
-
81
- return @port
82
- end
83
-
84
- # @returns [Async::HTTP::Endpoint] The endpoint the driver is listening on.
85
- def endpoint
86
- Async::HTTP::Endpoint.parse("http://localhost:#{port}")
33
+ def headless?
34
+ @options.fetch(:headless, true)
87
35
  end
88
36
  end
89
37
  end
@@ -3,10 +3,15 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2023, by Samuel Williams.
5
5
 
6
+ require 'async/actor'
7
+ require 'async/pool'
8
+
9
+ require_relative '../session'
10
+
6
11
  module Async
7
12
  module WebDriver
8
13
  module Bridge
9
- # A pool of sessions.
14
+ # A pool of sessions, constructed from a bridge, which instantiates drivers as needed. Drivers are capable of supporting 1 ore more sessions.
10
15
  #
11
16
  # ``` ruby
12
17
  # begin
@@ -17,90 +22,139 @@ module Async
17
22
  # end
18
23
  # ```
19
24
  class Pool
20
- # Create a new session pool and start it.
21
- # @parameter bridge [Bridge] The bridge to use to create sessions.
22
- # @parameter capabilities [Hash] The capabilities to use when creating sessions.
23
- # @returns [Pool] The pool.
24
- def self.start(bridge, **options)
25
- self.new(bridge, **options).tap do |pool|
26
- pool.start
25
+ class BridgeController
26
+ def initialize(bridge, capabilities: bridge.default_capabilities)
27
+ @bridge = bridge
28
+ @capabilities = capabilities
29
+ @pool = Async::Pool::Controller.new(self)
30
+ end
31
+
32
+ class SessionCache
33
+ def initialize(driver, capabilities)
34
+ @driver = driver
35
+ @capabilities = capabilities
36
+
37
+ @client = driver.client
38
+ @sessions = []
39
+ end
40
+
41
+ def viable?
42
+ @driver&.viable?
43
+ end
44
+
45
+ def reusable?
46
+ @driver&.reusable?
47
+ end
48
+
49
+ def close
50
+ if @driver
51
+ @driver.close
52
+ @driver = nil
53
+ end
54
+
55
+ if @client
56
+ @client.close
57
+ @client = nil
58
+ end
59
+
60
+ if @sessions
61
+ @sessions = nil
62
+ end
63
+ end
64
+
65
+ def concurrency
66
+ @driver.concurrency
67
+ end
68
+
69
+ def acquire
70
+ if @sessions.empty?
71
+ session = @client.post("session", {capabilities: @capabilities})
72
+ session[:cache] = self
73
+ session[:endpoint] = @driver.endpoint
74
+
75
+ return session
76
+ else
77
+ return @sessions.pop
78
+ end
79
+ end
80
+
81
+ def release(session)
82
+ @sessions.push(session)
83
+ end
84
+ end
85
+
86
+ # Constructor for the pool.
87
+ def call
88
+ SessionCache.new(@bridge.start, @capabilities)
89
+ end
90
+
91
+ def acquire
92
+ session_cache = @pool.acquire
93
+
94
+ return session_cache.acquire
95
+ end
96
+
97
+ def release(session)
98
+ session_cache = session[:cache]
99
+
100
+ session_cache.release(session)
101
+
102
+ @pool.release(session_cache)
103
+ end
104
+
105
+ def retire(session)
106
+ session_cache = session[:cache]
107
+
108
+ session_cache.release(session)
109
+
110
+ @pool.retire(session_cache)
111
+ end
112
+
113
+ def close
114
+ if @pool
115
+ @pool.close
116
+ @pool = nil
117
+ end
27
118
  end
28
119
  end
29
120
 
30
121
  # Initialize the session pool.
31
122
  # @parameter bridge [Bridge] The bridge to use to create sessions.
32
- # @parameter capabilities [Hash] The capabilities to use when creating sessions.
33
- # @parameter minimum [Integer] The minimum number of sessions to keep open.
34
- def initialize(bridge, capabilities: bridge.default_capabilities, minimum: 2)
35
- @bridge = bridge
36
- @capabilities = capabilities
37
- @minimum = minimum
38
-
39
- @thread = nil
40
-
41
- @waiting = Thread::Queue.new
42
- @sessions = Thread::Queue.new
123
+ def initialize(...)
124
+ @controller = Async::Actor.new(BridgeController.new(...))
43
125
  end
44
126
 
45
127
  # Close the session pool.
46
128
  def close
47
- if @waiting
48
- @waiting.close
129
+ @controller.close
130
+ end
131
+
132
+ class CachedWrapper < Session
133
+ def pool
134
+ @options[:pool]
49
135
  end
50
136
 
51
- if @thread
52
- @thread.join
53
- @thread = nil
137
+ def payload
138
+ @options[:payload]
54
139
  end
55
140
 
56
- if @sessions
57
- @sessions.close
58
- end
59
- end
60
-
61
- private def prepare_session(client)
62
- client.post("session", {capabilities: @capabilities})
63
- end
64
-
65
- # Start the session pool.
66
- def start
67
- @thread ||= Thread.new do
68
- Sync do
69
- @bridge.start
70
-
71
- client = Client.open(@bridge.endpoint)
72
-
73
- @minimum.times do
74
- @waiting << true
75
- end
76
-
77
- while @waiting.pop
78
- session = prepare_session(client)
79
- @sessions << session
80
- end
81
- ensure
82
- client&.close
83
- @bridge.close
141
+ def close
142
+ unless self.pool.reuse(self)
143
+ super
84
144
  end
85
145
  end
86
146
  end
87
147
 
88
148
  # Open a session.
89
149
  def session(&block)
90
- @waiting << true
150
+ payload = @controller.acquire
91
151
 
92
- reply = @sessions.pop
93
-
94
- session = Session.open(@bridge.endpoint, reply["sessionId"], reply["capabilities"])
152
+ session = CachedWrapper.open(payload[:endpoint], payload["sessionId"], payload["capabilities"], pool: self, payload: payload)
95
153
 
96
154
  return session unless block_given?
97
155
 
98
156
  begin
99
157
  yield session
100
-
101
- # Try to reuse the session for extreme performance:
102
- reuse(session)
103
- session = nil
104
158
  ensure
105
159
  session&.close
106
160
  end
@@ -109,7 +163,9 @@ module Async
109
163
  def reuse(session)
110
164
  session.reset!
111
165
 
112
- @sessions << {"sessionId" => session.id, "capabilities" => session.capabilities}
166
+ @controller.release(session.payload)
167
+
168
+ return true
113
169
  end
114
170
  end
115
171
  end
@@ -3,6 +3,8 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2023, by Samuel Williams.
5
5
 
6
+ require_relative 'driver'
7
+
6
8
  module Async
7
9
  module WebDriver
8
10
  module Bridge
@@ -72,6 +74,23 @@ module Async
72
74
  # Done.
73
75
  end
74
76
  end
77
+
78
+ class ProcessDriver < Driver
79
+ def initialize(endpoint, process)
80
+ super(endpoint)
81
+
82
+ @process = process
83
+ end
84
+
85
+ def close
86
+ super
87
+
88
+ if @process
89
+ @process.close
90
+ @process = nil
91
+ end
92
+ end
93
+ end
75
94
  end
76
95
  end
77
96
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'bridge/chrome'
7
7
  require_relative 'bridge/firefox'
8
8
 
9
+ require_relative 'error'
10
+
9
11
  module Async
10
12
  module WebDriver
11
13
  # A bridge is a process that can be used to communicate with a browser.
@@ -25,6 +27,42 @@ module Async
25
27
  yield klass
26
28
  end
27
29
  end
30
+
31
+ # The environment variable used to select a bridge.
32
+ #
33
+ # ```
34
+ # ASYNC_WEBDRIVER_BRIDGE=Chrome
35
+ # ASYNC_WEBDRIVER_BRIDGE=Firefox
36
+ # ```
37
+ ASYNC_WEBDRIVER_BRIDGE = 'ASYNC_WEBDRIVER_BRIDGE'
38
+
39
+ # The environment variable used to disable headless mode.
40
+ #
41
+ # ```
42
+ # ASYNC_WEBDRIVER_BRIDGE_HEADLESS=false
43
+ # ```
44
+ ASYNC_WEBDRIVER_BRIDGE_HEADLESS = 'ASYNC_WEBDRIVER_BRIDGE_HEADLESS'
45
+
46
+ class UnsupportedError < Error
47
+ end
48
+
49
+ # @returns [Bridge] The default bridge to use.
50
+ def self.default(env = ENV, **options)
51
+ if env[ASYNC_WEBDRIVER_BRIDGE_HEADLESS] == "false"
52
+ options[:headless] = false
53
+ end
54
+
55
+ if name = env[ASYNC_WEBDRIVER_BRIDGE]
56
+ self.const_get(name).mew(**options)
57
+ else
58
+ ALL.each do |klass|
59
+ instance = klass.new(**options)
60
+ return instance if instance.supported?
61
+ end
62
+
63
+ raise UnsupportedError, "No supported bridge found!"
64
+ end
65
+ end
28
66
  end
29
67
  end
30
68
  end
@@ -60,7 +60,7 @@ module Async
60
60
  def session(capabilities, &block)
61
61
  reply = post("session", {capabilities: capabilities})
62
62
 
63
- session = Session.new(@delegate, reply["sessionId"], reply["value"])
63
+ session = Session.new(@delegate, reply["sessionId"], reply["capabilities"])
64
64
 
65
65
  return session unless block_given?
66
66
 
@@ -7,36 +7,6 @@ require_relative 'version'
7
7
 
8
8
  module Async
9
9
  module WebDriver
10
- # Error Code HTTP Status JSON Error Code Description
11
- # element click intercepted 400 element click intercepted The Element Click command could not be completed because the element receiving the events is obscuring the element that was requested clicked.
12
- # element not interactable 400 element not interactable A command could not be completed because the element is not pointer- or keyboard interactable.
13
- # insecure certificate 400 insecure certificate Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired or invalid TLS certificate.
14
- # invalid argument 400 invalid argument The arguments passed to a command are either invalid or malformed.
15
- # invalid cookie domain 400 invalid cookie domain An illegal attempt was made to set a cookie under a different domain than the current page.
16
- # invalid element state 400 invalid element state A command could not be completed because the element is in an invalid state, e.g. attempting to clear an element that isn’t both editable and resettable.
17
- # invalid selector 400 invalid selector Argument was an invalid selector.
18
- # invalid session id 404 invalid session id Occurs if the given session id is not in the list of active sessions, meaning the session either does not exist or that it’s not active.
19
- # javascript error 500 javascript error An error occurred while executing JavaScript supplied by the user.
20
- # move target out of bounds 500 move target out of bounds The target for mouse interaction is not in the browser’s viewport and cannot be brought into that viewport.
21
- # no such alert 404 no such alert An attempt was made to operate on a modal dialog when one was not open.
22
- # no such cookie 404 no such cookie No cookie matching the given path name was found amongst the associated cookies of the current browsing context’s active document.
23
- # no such element 404 no such element An element could not be located on the page using the given search parameters.
24
- # no such frame 404 no such frame A command to switch to a frame could not be satisfied because the frame could not be found.
25
- # no such window 404 no such window A command to switch to a window could not be satisfied because the window could not be found.
26
- # no such shadow root 404 no such shadow root The element does not have a shadow root.
27
- # script timeout error 500 script timeout A script did not complete before its timeout expired.
28
- # session not created 500 session not created A new session could not be created.
29
- # stale element reference 404 stale element reference A command failed because the referenced element is no longer attached to the DOM.
30
- # detached shadow root 404 detached shadow root A command failed because the referenced shadow root is no longer attached to the DOM.
31
- # timeout 500 timeout An operation did not complete before its timeout expired.
32
- # unable to set cookie 500 unable to set cookie A command to set a cookie’s value could not be satisfied.
33
- # unable to capture screen 500 unable to capture screen A screen capture was made impossible.
34
- # unexpected alert open 500 unexpected alert open A modal dialog was open, blocking this operation.
35
- # unknown command 404 unknown command A command could not be executed because the remote end is not aware of it.
36
- # unknown error 500 unknown error An unknown error occurred in the remote end while processing the command.
37
- # unknown method 405 unknown method The requested command matched a known URL but did not match any method for that URL.
38
- # unsupported operation 500 unsupported operation Indicates that a command that should have executed properly cannot be supported for some reason.
39
-
40
10
  class Error < StandardError
41
11
  end
42
12
 
@@ -3,8 +3,6 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2023, by Samuel Williams.
5
5
 
6
- require 'base64'
7
-
8
6
  module Async
9
7
  module WebDriver
10
8
  # A locator is used to find elements in the DOM.
@@ -3,8 +3,6 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2023, by Samuel Williams.
5
5
 
6
- require 'base64'
7
-
8
6
  module Async
9
7
  module WebDriver
10
8
  module Scope
@@ -3,8 +3,6 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2023, by Samuel Williams.
5
5
 
6
- require 'base64'
7
-
8
6
  require_relative '../xpath'
9
7
 
10
8
  module Async
@@ -30,8 +30,9 @@ module Async
30
30
  # @returns [Session] The session if no block is given.
31
31
  def self.open(endpoint, *arguments, **options)
32
32
  client = self.new(
33
- Async::HTTP::Client.open(endpoint, **options),
34
- *arguments
33
+ Async::HTTP::Client.open(endpoint),
34
+ *arguments,
35
+ **options
35
36
  )
36
37
 
37
38
  return client unless block_given?
@@ -47,10 +48,12 @@ module Async
47
48
  # @parameter delegate [Protocol::HTTP::Middleware] The underlying HTTP client (or wrapper).
48
49
  # @parameter id [String] The session identifier.
49
50
  # @parameter capabilities [Hash] The capabilities of the session.
50
- def initialize(delegate, id, capabilities)
51
+ def initialize(delegate, id, capabilities, **options)
51
52
  @delegate = delegate
52
53
  @id = id
53
54
  @capabilities = capabilities
55
+
56
+ @options = options
54
57
  end
55
58
 
56
59
  def inspect
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Async
7
7
  module WebDriver
8
- VERSION = "0.3.1"
8
+ VERSION = "0.5.0"
9
9
  end
10
10
  end
data/license.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2023, by Samuel Williams.
3
+ Copyright, 2023-2024, by Samuel Williams.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -18,6 +18,10 @@ Please see the [project documentation](https://socketry.github.io/async-webdrive
18
18
 
19
19
  - [GitHub Actions Integrations](https://socketry.github.io/async-webdriver/guides/github-actions-integration/index) - This guide explains how to use `async-webdriver` with GitHub Actions.
20
20
 
21
+ ## See Also
22
+
23
+ - [sus-fixtures-async-webdriver](https://github.com/socketry/sus-fixtures-async-webdriver) - STandard fixtures for testing web applications with `async-webdriver`.
24
+
21
25
  ## Contributing
22
26
 
23
27
  We welcome contributions to this project.
@@ -34,4 +38,4 @@ This project uses the [Developer Certificate of Origin](https://developercertifi
34
38
 
35
39
  ### Contributor Covenant
36
40
 
37
- This project is governed by [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.
41
+ This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-webdriver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -37,22 +37,78 @@ cert_chain:
37
37
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
38
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
39
  -----END CERTIFICATE-----
40
- date: 2023-12-04 00:00:00.000000000 Z
40
+ date: 2024-05-04 00:00:00.000000000 Z
41
41
  dependencies:
42
+ - !ruby/object:Gem::Dependency
43
+ name: async-actor
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '0.1'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '0.1'
42
56
  - !ruby/object:Gem::Dependency
43
57
  name: async-http
44
58
  requirement: !ruby/object:Gem::Requirement
45
59
  requirements:
46
60
  - - "~>"
47
61
  - !ruby/object:Gem::Version
48
- version: '0.42'
62
+ version: '0.61'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '0.61'
70
+ - !ruby/object:Gem::Dependency
71
+ name: async-pool
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0.4'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.4'
84
+ - !ruby/object:Gem::Dependency
85
+ name: async-websocket
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '0.25'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '0.25'
98
+ - !ruby/object:Gem::Dependency
99
+ name: base64
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '0.2'
49
105
  type: :runtime
50
106
  prerelease: false
51
107
  version_requirements: !ruby/object:Gem::Requirement
52
108
  requirements:
53
109
  - - "~>"
54
110
  - !ruby/object:Gem::Version
55
- version: '0.42'
111
+ version: '0.2'
56
112
  description:
57
113
  email:
58
114
  executables: []
@@ -62,6 +118,7 @@ files:
62
118
  - lib/async/webdriver.rb
63
119
  - lib/async/webdriver/bridge.rb
64
120
  - lib/async/webdriver/bridge/chrome.rb
121
+ - lib/async/webdriver/bridge/driver.rb
65
122
  - lib/async/webdriver/bridge/firefox.rb
66
123
  - lib/async/webdriver/bridge/generic.rb
67
124
  - lib/async/webdriver/bridge/pool.rb
@@ -87,12 +144,13 @@ files:
87
144
  - lib/async/webdriver/xpath.rb
88
145
  - license.md
89
146
  - readme.md
90
- homepage: https://github.com/socketry/async-webdriver/
147
+ homepage: https://github.com/socketry/async-webdriver
91
148
  licenses:
92
149
  - MIT
93
150
  metadata:
94
151
  documentation_uri: https://socketry.github.io/async-webdriver/
95
152
  funding_uri: https://github.com/sponsors/ioquatix
153
+ source_code_uri: https://github.com/socketry/async-webdriver.git
96
154
  post_install_message:
97
155
  rdoc_options: []
98
156
  require_paths:
@@ -101,14 +159,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
159
  requirements:
102
160
  - - ">="
103
161
  - !ruby/object:Gem::Version
104
- version: '3.0'
162
+ version: '3.1'
105
163
  required_rubygems_version: !ruby/object:Gem::Requirement
106
164
  requirements:
107
165
  - - ">="
108
166
  - !ruby/object:Gem::Version
109
167
  version: '0'
110
168
  requirements: []
111
- rubygems_version: 3.4.22
169
+ rubygems_version: 3.5.3
112
170
  signing_key:
113
171
  specification_version: 4
114
172
  summary: A native library implementing the W3C WebDriver client specification.
metadata.gz.sig CHANGED
Binary file