Skip to content

Commit 91b3e8c

Browse files
committed
Add virtual_display package to SeleniumBase
1 parent f593395 commit 91b3e8c

File tree

10 files changed

+863
-0
lines changed

10 files changed

+863
-0
lines changed

seleniumbase/virtual_display/__init__.py

Whitespace-only changes.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import fnmatch
2+
import os
3+
import tempfile
4+
import time
5+
from threading import Lock
6+
from seleniumbase.virtual_display.easyprocess import EasyProcess
7+
from seleniumbase.virtual_display import xauth
8+
9+
mutex = Lock()
10+
RANDOMIZE_DISPLAY_NR = False
11+
if RANDOMIZE_DISPLAY_NR:
12+
import random
13+
random.seed()
14+
MIN_DISPLAY_NR = 1000
15+
USED_DISPLAY_NR_LIST = []
16+
17+
18+
class AbstractDisplay(EasyProcess):
19+
'''
20+
Common parent for Xvfb and Xephyr
21+
'''
22+
def __init__(self, use_xauth=False):
23+
mutex.acquire()
24+
try:
25+
self.display = self.search_for_display()
26+
while self.display in USED_DISPLAY_NR_LIST:
27+
self.display += 1
28+
USED_DISPLAY_NR_LIST.append(self.display)
29+
finally:
30+
mutex.release()
31+
if xauth and not xauth.is_installed():
32+
raise xauth.NotFoundError()
33+
self.use_xauth = use_xauth
34+
self._old_xauth = None
35+
self._xauth_filename = None
36+
EasyProcess.__init__(self, self._cmd)
37+
38+
@property
39+
def new_display_var(self):
40+
return ':%s' % (self.display)
41+
42+
@property
43+
def _cmd(self):
44+
raise NotImplementedError()
45+
46+
def lock_files(self):
47+
tmpdir = '/tmp'
48+
pattern = '.X*-lock'
49+
# remove path.py dependency
50+
names = fnmatch.filter(os.listdir(tmpdir), pattern)
51+
ls = [os.path.join(tmpdir, child) for child in names]
52+
ls = [p for p in ls if os.path.isfile(p)]
53+
return ls
54+
55+
def search_for_display(self):
56+
# search for free display
57+
ls = [int(x.split('X')[1].split('-')[0]) for x in self.lock_files()]
58+
if len(ls):
59+
display = max(MIN_DISPLAY_NR, max(ls) + 3)
60+
else:
61+
display = MIN_DISPLAY_NR
62+
if RANDOMIZE_DISPLAY_NR:
63+
display += random.randint(0, 100)
64+
return display
65+
66+
def redirect_display(self, on):
67+
'''
68+
on:
69+
* True -> set $DISPLAY to virtual screen
70+
* False -> set $DISPLAY to original screen
71+
72+
:param on: bool
73+
'''
74+
d = self.new_display_var if on else self.old_display_var
75+
if d is None:
76+
del os.environ['DISPLAY']
77+
else:
78+
os.environ['DISPLAY'] = d
79+
80+
def start(self):
81+
'''
82+
start display
83+
84+
:rtype: self
85+
'''
86+
if self.use_xauth:
87+
self._setup_xauth()
88+
EasyProcess.start(self)
89+
90+
# https://github.com/ponty/PyVirtualDisplay/issues/2
91+
# https://github.com/ponty/PyVirtualDisplay/issues/14
92+
self.old_display_var = os.environ.get('DISPLAY', None)
93+
94+
self.redirect_display(True)
95+
# wait until X server is active
96+
# TODO: better method
97+
time.sleep(0.1)
98+
return self
99+
100+
def stop(self):
101+
'''
102+
stop display
103+
104+
:rtype: self
105+
'''
106+
self.redirect_display(False)
107+
EasyProcess.stop(self)
108+
if self.use_xauth:
109+
self._clear_xauth()
110+
return self
111+
112+
def _setup_xauth(self):
113+
'''
114+
Set up the Xauthority file and the XAUTHORITY environment variable.
115+
'''
116+
handle, filename = tempfile.mkstemp(prefix='PyVirtualDisplay.',
117+
suffix='.Xauthority')
118+
self._xauth_filename = filename
119+
os.close(handle)
120+
# Save old environment
121+
self._old_xauth = {}
122+
self._old_xauth['AUTHFILE'] = os.getenv('AUTHFILE')
123+
self._old_xauth['XAUTHORITY'] = os.getenv('XAUTHORITY')
124+
125+
os.environ['AUTHFILE'] = os.environ['XAUTHORITY'] = filename
126+
cookie = xauth.generate_mcookie()
127+
xauth.call('add', self.new_display_var, '.', cookie)
128+
129+
def _clear_xauth(self):
130+
'''
131+
Clear the Xauthority file and restore the environment variables.
132+
'''
133+
os.remove(self._xauth_filename)
134+
for varname in ['AUTHFILE', 'XAUTHORITY']:
135+
if self._old_xauth[varname] is None:
136+
del os.environ[varname]
137+
else:
138+
os.environ[varname] = self._old_xauth[varname]
139+
self._old_xauth = None
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
This module contains a customized version of pyvirtualdisplay.
3+
These helper methods SHOULD NOT be called directly from tests.
4+
"""
5+
from seleniumbase.virtual_display.abstractdisplay import AbstractDisplay
6+
from seleniumbase.virtual_display.xephyr import XephyrDisplay
7+
from seleniumbase.virtual_display.xvfb import XvfbDisplay
8+
from seleniumbase.virtual_display.xvnc import XvncDisplay
9+
10+
11+
class Display(AbstractDisplay):
12+
'''
13+
Common class
14+
15+
:param color_depth: [8, 16, 24, 32]
16+
:param size: screen size (width,height)
17+
:param bgcolor: background color ['black' or 'white']
18+
:param visible: True -> Xephyr, False -> Xvfb
19+
:param backend: 'xvfb', 'xvnc' or 'xephyr', ignores ``visible``
20+
:param xauth: If a Xauthority file should be created.
21+
'''
22+
def __init__(self, backend=None, visible=False, size=(1024, 768),
23+
color_depth=24, bgcolor='black', use_xauth=False, **kwargs):
24+
self.color_depth = color_depth
25+
self.size = size
26+
self.bgcolor = bgcolor
27+
self.screen = 0
28+
self.process = None
29+
self.display = None
30+
self.visible = visible
31+
self.backend = backend
32+
33+
if not self.backend:
34+
if self.visible:
35+
self.backend = 'xephyr'
36+
else:
37+
self.backend = 'xvfb'
38+
39+
self._obj = self.display_class(
40+
size=size,
41+
color_depth=color_depth,
42+
bgcolor=bgcolor,
43+
**kwargs)
44+
AbstractDisplay.__init__(self, use_xauth=use_xauth)
45+
46+
@property
47+
def display_class(self):
48+
assert self.backend
49+
if self.backend == 'xvfb':
50+
cls = XvfbDisplay
51+
if self.backend == 'xvnc':
52+
cls = XvncDisplay
53+
if self.backend == 'xephyr':
54+
cls = XephyrDisplay
55+
cls.check_installed()
56+
return cls
57+
58+
@property
59+
def _cmd(self):
60+
self._obj.display = self.display
61+
return self._obj._cmd

0 commit comments

Comments
 (0)