|
| 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