Skip to content

Commit 3fbceb3

Browse files
authored
Merge pull request #7 from suskind/add_navigation_converter
add navigation_converter
2 parents 5c0c5ba + a95b7b0 commit 3fbceb3

3 files changed

Lines changed: 350 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
utils/tmp
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Navigation Converter
2+
3+
## Converts between Probely's Navigation sequences and Selenium.
4+
5+
### Required options
6+
7+
- `--convert/-c`: `selenium2probely` or `probely2selenium`
8+
- `--input/-i`: `/path/to/original_file`
9+
- `--output/-o`: `/path/to/destination_file` (Coverting to Selenium requires `.side` extension)
10+
11+
### From Selenium to Probely's Navigation sequence
12+
13+
```sh
14+
$ python3 ./navigation_converter.py -c 'selenium2probely' -i /tmp/selenium_testfile.side -o /tmp/probely_navigation.json
15+
```
16+
17+
18+
### From Probely's Navigation sequence to Selenium
19+
20+
```sh
21+
$ python3 ./navigation_converter.py -c 'probely2selenium' -i /tmp/probely_navigation.json -o /tmp/selenium_testfile.side
22+
```
23+

utils/navigation_converter.py

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
#!/usr/bin/env python
2+
"""
3+
Converts between Probely's Navigation sequences and Selenium.
4+
5+
We can not guarantee that the conversion will be 100% compatible.
6+
"""
7+
import argparse
8+
import json
9+
import re
10+
import uuid
11+
import os
12+
from time import time
13+
14+
15+
items_not_supported = []
16+
17+
18+
def getUUID():
19+
return str(uuid.uuid4())
20+
21+
22+
def getSeleniumCssAndXPath(targets, target):
23+
css = None
24+
xpath = None
25+
for item in targets:
26+
if item[1] == 'css:finder':
27+
css = item[0].replace('css=', '')
28+
if item[1] == 'xpath:position':
29+
xpath = item[0].replace('xpath=', '')
30+
31+
if css is None and target:
32+
if target.startswith('name='):
33+
css = '[{}]'.format(target)
34+
elif target.startswith('id='):
35+
css = '[{}]'.format(target)
36+
return (css, xpath)
37+
38+
39+
def convertProbely2Selenium(data, inputFp, outputFp=None):
40+
name = os.path.basename(inputFp.name)
41+
obj = {
42+
'id': getUUID(),
43+
'version': '2.0',
44+
'name': name,
45+
'url': None,
46+
'tests': [{
47+
'id': getUUID(),
48+
'name': name,
49+
'commands': []
50+
}],
51+
'suites': [{
52+
'id': getUUID(),
53+
'name': 'Default Suite',
54+
'persistSession': False,
55+
'parallel': False,
56+
'timeout': 300,
57+
'tests': [getUUID()]
58+
}],
59+
'urls': [],
60+
'plugins': []
61+
}
62+
if len(data) == 0:
63+
raise Exception('No data in Probely recording file to be converted.')
64+
65+
for idx, item in enumerate(data):
66+
if idx == 0:
67+
if item['type'] == 'goto':
68+
m = re.search('(https?://[^/]+)(/?.*)', item.get('url'))
69+
url_base = m.group(1)
70+
url_path = m.group(2)
71+
obj['url'] = url_base
72+
obj['urls'].append(url_base)
73+
obj['tests'][0]['commands'].append({
74+
'id': getUUID(),
75+
'comment': '',
76+
'command': 'open',
77+
'target': url_path,
78+
'targets': [],
79+
'value': ''
80+
})
81+
obj['tests'][0]['commands'].append({
82+
'id': getUUID(),
83+
'comment': '',
84+
'command': 'setWindowSize',
85+
'target': '{}x{}'.format(item.get('windowWidth', '1800'), item.get('windowHeight', '1200')),
86+
'targets': [],
87+
'value': ''
88+
})
89+
else:
90+
raise Exception('First item must be a "goto" with an URL')
91+
else:
92+
css = 'css={}'.format(item.get('css'))
93+
xpath = 'xpath={}'.format(item.get('xpath'))
94+
if item.get('type') == 'click' or item.get('type') == 'bclick':
95+
obj['tests'][0]['commands'].append({
96+
'id': getUUID(),
97+
'comment': '',
98+
'command': 'click',
99+
'target': css,
100+
'targets': [
101+
[css, 'css:finder'],
102+
[xpath, 'xpath:position']
103+
],
104+
'value': ''
105+
})
106+
elif item.get('type') == 'mouseover':
107+
obj['tests'][0]['commands'].append({
108+
'id': getUUID(),
109+
'comment': '',
110+
'command': 'mouseOver',
111+
'target': css,
112+
'targets': [
113+
[css, 'css:finder'],
114+
[xpath, 'xpath:position']
115+
],
116+
'value': ''
117+
})
118+
elif item.get('type') == 'fill_value':
119+
obj['tests'][0]['commands'].append({
120+
'id': getUUID(),
121+
'comment': '',
122+
'command': 'type',
123+
'target': css,
124+
'targets': [
125+
[css, 'css:finder'],
126+
[xpath, 'xpath:position']
127+
],
128+
'value': item.get('value', '')
129+
})
130+
elif item.get('type') == 'press_key' and item.get('value') == 13:
131+
obj['tests'][0]['commands'].append({
132+
'id': getUUID(),
133+
'comment': '',
134+
'command': 'sendKeys',
135+
'target': css,
136+
'targets': [
137+
[css, 'css:finder'],
138+
[xpath, 'xpath:position']
139+
],
140+
'value': '${KEY_ENTER}'
141+
})
142+
elif item.get('type') == 'change' and item.get('subtype') == 'select':
143+
obj['tests'][0]['commands'].append({
144+
'id': getUUID(),
145+
'comment': '',
146+
'command': 'select',
147+
'target': css,
148+
'targets': [
149+
[css, 'css:finder'],
150+
[xpath, 'xpath:position']
151+
],
152+
'value': 'value={}'.format(item.get('value', ''))
153+
})
154+
elif item.get('type') == 'change' and item.get('subtype') == 'check':
155+
obj['tests'][0]['commands'].append({
156+
'id': getUUID(),
157+
'comment': '',
158+
'command': 'click',
159+
'target': css,
160+
'targets': [
161+
[css, 'css:finder'],
162+
[xpath, 'xpath:position']
163+
],
164+
'value': ''
165+
})
166+
elif item.get('type') == 'dbclick':
167+
obj['tests'][0]['commands'].append({
168+
'id': getUUID(),
169+
'comment': '',
170+
'command': 'doubleClick',
171+
'target': css,
172+
'targets': [
173+
[css, 'css:finder'],
174+
[xpath, 'xpath:position']
175+
],
176+
'value': ''
177+
})
178+
else:
179+
items_not_supported.append('{} => {}'.format(idx, item.get('type')))
180+
181+
return obj
182+
183+
184+
def convertSelenium2Probely(data):
185+
obj = []
186+
timestamp = int(time()) * 1000
187+
tests_arr = data.get('tests')
188+
if tests_arr is None or len(tests_arr) == 0:
189+
raise Exception('No tests in Selenium file to be converted.')
190+
191+
commands_arr = tests_arr[0].get('commands')
192+
if commands_arr is None or len(commands_arr) == 0:
193+
raise Exception('No commands in Selenium file to be converted.')
194+
195+
url_base = data.get('url')
196+
# get first url
197+
first_command = commands_arr[0].get('command')
198+
second_command = commands_arr[1].get('command')
199+
url_path = commands_arr[0].get('target')
200+
dimensions = commands_arr[1].get('target')
201+
if first_command != 'open' or second_command != 'setWindowSize':
202+
raise Exception('Selenium first command must be "open" or second command must be "setWindowSize"')
203+
if url_path is None:
204+
raise Exception('Selenium first command must have a "target"')
205+
206+
windowWidth = 1800
207+
windowHeight = 1200
208+
if dimensions:
209+
m = re.search('([0-9]+)x([0-9]+)', dimensions)
210+
windowWidth = int(m.group(1))
211+
windowHeight = int(m.group(2))
212+
213+
obj.append({
214+
'type': 'goto',
215+
'urlType': 'force',
216+
'url': '{}{}'.format(url_base, url_path),
217+
'timestamp': timestamp,
218+
'windowWidth': windowWidth,
219+
'windowHeight': windowHeight
220+
})
221+
for idx, item in enumerate(commands_arr):
222+
if idx == 0 or idx == 1:
223+
continue
224+
else:
225+
css, xpath = getSeleniumCssAndXPath(item.get('targets'), item.get('target'))
226+
if css is None:
227+
continue
228+
if item.get('command') == 'click':
229+
timestamp += 1000
230+
obj.append({
231+
'timestamp': timestamp,
232+
'type': 'click',
233+
'css': css,
234+
'xpath': xpath,
235+
'value': '',
236+
'frame': None
237+
})
238+
elif item.get('command') == 'mouseOver':
239+
timestamp += 1000
240+
obj.append({
241+
'timestamp': timestamp,
242+
'type': 'mouseover',
243+
'css': css,
244+
'xpath': xpath,
245+
'value': '',
246+
'frame': None
247+
})
248+
elif item.get('command') == 'type':
249+
timestamp += 1000
250+
obj.append({
251+
'timestamp': timestamp,
252+
'type': 'fill_value',
253+
'css': css,
254+
'xpath': xpath,
255+
'value': item.get('value', ''),
256+
'frame': None
257+
})
258+
elif item.get('command') == 'sendKeys' and item.get('value') == '${KEY_ENTER}':
259+
timestamp += 1000
260+
obj.append({
261+
'timestamp': timestamp,
262+
'type': 'press_key',
263+
'css': css,
264+
'xpath': xpath,
265+
'value': 13,
266+
'frame': None
267+
})
268+
elif item.get('command') == 'select':
269+
timestamp += 1000
270+
select_value = item.get('value', '').replace('label=', '').replace('value=', '')
271+
obj.append({
272+
'timestamp': timestamp,
273+
'type': 'change',
274+
'subtype': 'select',
275+
'css': css,
276+
'xpath': xpath,
277+
'value': select_value,
278+
'selected': 1, # hardcoded, not correct
279+
'frame': None
280+
})
281+
elif item.get('command') == 'doubleClick':
282+
timestamp += 1000
283+
obj.append({
284+
'timestamp': timestamp,
285+
'type': 'dblclick',
286+
'css': css,
287+
'xpath': xpath,
288+
'value': '',
289+
'frame': None
290+
})
291+
else:
292+
items_not_supported.append('{} => {}'.format(idx, item.get('command')))
293+
294+
return obj
295+
296+
297+
def run():
298+
parser = argparse.ArgumentParser(description='Converts Probely navigation sequences to selenium or selenium to Probely navigation sequences')
299+
parser.add_argument('-c', '--convert', help='<probely2selenium>/<selenium2probely>', choices=['probely2selenium', 'selenium2probely'])
300+
parser.add_argument('-i', '--input', help='Input file', type=argparse.FileType('r'))
301+
parser.add_argument('-o', '--output', help='Output file', type=argparse.FileType('w'))
302+
args = parser.parse_args()
303+
304+
if args.convert == 'probely2selenium' and not args.output.name.endswith('.side'):
305+
raise Exception('Error: Selenium file name must use ".side" extension')
306+
307+
input_data = json.load(args.input)
308+
309+
if args.convert == 'probely2selenium':
310+
out_data = convertProbely2Selenium(input_data, args.input)
311+
elif args.convert == 'selenium2probely':
312+
out_data = convertSelenium2Probely(input_data)
313+
314+
final_json = json.dumps(out_data, indent=2)
315+
args.output.write(final_json)
316+
317+
if len(items_not_supported) > 0:
318+
print('Warning:')
319+
print('Items not supported/converted from positions: {}'.format(str(items_not_supported)))
320+
321+
print('Done.')
322+
print('File converted to file: {}'.format(args.output.name))
323+
324+
325+
if __name__ == '__main__':
326+
run()

0 commit comments

Comments
 (0)