1
1
import argparse
2
2
import json
3
3
import os
4
+ import re
4
5
import socket
5
6
import subprocess
7
+ import threading
6
8
import time
7
9
from contextlib import suppress
8
10
from urllib .parse import urlparse
12
14
from mozprofile import Preferences , Profile
13
15
from mozrunner import FirefoxRunner
14
16
15
- from support .network import get_free_port
16
-
17
17
18
18
def get_arg_value (arg_names , args ):
19
19
"""Get an argument value from a list of arguments
@@ -232,6 +232,8 @@ def wait(self):
232
232
233
233
234
234
class Geckodriver :
235
+ PORT_RE = re .compile (b".*Listening on [^ :]*:(\d+)" )
236
+
235
237
def __init__ (self , configuration , hostname = None , extra_args = None ):
236
238
self .config = configuration ["webdriver" ]
237
239
self .requested_capabilities = configuration ["capabilities" ]
@@ -241,12 +243,11 @@ def __init__(self, configuration, hostname=None, extra_args=None):
241
243
242
244
self .command = None
243
245
self .proc = None
244
- self .port = get_free_port ()
246
+ self .port = None
247
+ self .reader_thread = None
245
248
246
- capabilities = {"alwaysMatch" : self .requested_capabilities }
247
- self .session = webdriver .Session (
248
- self .hostname , self .port , capabilities = capabilities
249
- )
249
+ self .capabilities = {"alwaysMatch" : self .requested_capabilities }
250
+ self .session = None
250
251
251
252
@property
252
253
def remote_agent_port (self ):
@@ -257,14 +258,20 @@ def remote_agent_port(self):
257
258
258
259
def start (self ):
259
260
self .command = (
260
- [self .config ["binary" ], "--port" , str ( self . port ) ]
261
+ [self .config ["binary" ], "--port" , "0" ]
261
262
+ self .config ["args" ]
262
263
+ self .extra_args
263
264
)
264
265
265
266
print (f"Running command: { ' ' .join (self .command )} " )
266
- self .proc = subprocess .Popen (self .command , env = self .env )
267
+ self .proc = subprocess .Popen (self .command , env = self .env , stdout = subprocess . PIPE )
267
268
269
+ self .reader_thread = threading .Thread (
270
+ target = readOutputLine ,
271
+ args = (self .proc .stdout , self .processOutputLine ),
272
+ daemon = True ,
273
+ )
274
+ self .reader_thread .start ()
268
275
# Wait for the port to become ready
269
276
end_time = time .time () + 10
270
277
while time .time () < end_time :
@@ -273,24 +280,53 @@ def start(self):
273
280
raise ChildProcessError (
274
281
f"geckodriver terminated with code { returncode } "
275
282
)
276
- with socket .socket () as sock :
277
- if sock .connect_ex ((self .hostname , self .port )) == 0 :
278
- break
283
+ if self .port is not None :
284
+ with socket .socket () as sock :
285
+ if sock .connect_ex ((self .hostname , self .port )) == 0 :
286
+ break
287
+ else :
288
+ time .sleep (0.1 )
279
289
else :
290
+ if self .port is None :
291
+ raise OSError (
292
+ f"Failed to read geckodriver port started on { self .hostname } "
293
+ )
280
294
raise ConnectionRefusedError (
281
295
f"Failed to connect to geckodriver on { self .hostname } :{ self .port } "
282
296
)
283
297
298
+ self .session = webdriver .Session (
299
+ self .hostname , self .port , capabilities = self .capabilities
300
+ )
301
+
284
302
return self
285
303
286
- def stop (self ):
287
- self .delete_session ()
304
+ def processOutputLine (self , line ):
305
+ if self .port is None :
306
+ m = self .PORT_RE .match (line )
307
+ if m is not None :
308
+ self .port = int (m .groups ()[0 ])
288
309
310
+ def stop (self ):
311
+ if self .session is not None :
312
+ self .delete_session ()
289
313
if self .proc :
290
314
self .proc .kill ()
315
+ self .port = None
316
+ if self .reader_thread is not None :
317
+ self .reader_thread .join ()
291
318
292
319
def new_session (self ):
293
320
self .session .start ()
294
321
295
322
def delete_session (self ):
296
323
self .session .end ()
324
+
325
+
326
+ def readOutputLine (stream , callback ):
327
+ while True :
328
+ line = stream .readline ()
329
+ if not line :
330
+ break
331
+
332
+ callback (line )
0 commit comments