Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import os 

2import uuid 

3import re 

4from app import app, models, db 

5#from flask import current_app as app 

6from datetime import datetime 

7import xlsxwriter, xlrd, json, requests 

8from flask_login import current_user 

9from flask import flash 

10from sqlalchemy import func, desc 

11 

12# 

13# Default VALUES 

14# 

15CLASSNAME_TESTCASESEQUENCE = 'GC.CLASSES_TESTCASESEQUENCE' 

16CLASSNAME_TESTCASE = 'GC.CLASSES_TESTCASE' 

17CLASSNAME_TESTSTEP = 'GC.CLASSES_TESTSTEPMASTER' 

18 

19BROWSER_TYPE = 'FF' 

20TESTCASE_TYPE = 'Browser' 

21ACTIVITY_TYPE = 'GOTOURL' 

22#LOCATOR_TYPE = 'xpath' 

23 

24 

25# 

26# item categories 

27# 

28def getItemCategories(): 

29 categories = {} 

30 categories['main'] = [ 

31 'testrun', 

32 'testcase_sequence', 

33 'testcase', 

34 'teststep_sequence', 

35 'teststep', 

36 ] 

37 

38 return categories 

39 

40# 

41# get name of the item_type 

42# 

43def getItemType(item_type, plural=False): 

44 # main items  

45 if item_type == 'testrun': 

46 name = 'Testrun' 

47 elif item_type == 'testcase_sequence': 

48 name = 'Test Case Sequence' 

49 elif item_type == 'testcase': 

50 name = 'Test Case' 

51 elif item_type == 'teststep_sequence': 

52 name = 'Test Step Sequence' 

53 elif item_type == 'teststep': 

54 name = 'Test Step' 

55 else: 

56 # wrong item_type 

57 return '' 

58 

59 # check for plurals 

60 if plural: 

61 name += 's' 

62 

63 return name 

64 

65# 

66# generate choices of items 

67# 

68 

69def getTestCaseSequences(): 

70 return [(f'{item.uuid}', item.name) for item in models.TestCaseSequence.query.order_by(func.lower(models.TestCaseSequence.name)).all()] 

71 

72def getDataFiles(): 

73 return [(f'{item.uuid}', item.filename) for item in models.DataFile.query.order_by(func.lower(models.DataFile.filename)).all()] 

74 

75def getTestCases(): 

76 return [(f'{item.uuid}', item.name) for item in models.TestCase.query.order_by(func.lower(models.TestCase.name)).all()] 

77 

78def getTestStepSequences(): 

79 return [(f'{item.uuid}', item.name) for item in models.TestStepSequence.query.order_by(func.lower(models.TestStepSequence.name)).all()] 

80 

81def getTestSteps(item_id=None): 

82 return [(f'{item.uuid}', item.name) for item in models.TestStepExecution.query.order_by(func.lower(models.TestStepExecution.name)).all() \ 

83 if not item.teststep_sequence or item.teststep_sequence.uuid == item_id] 

84 

85def getClassNames(default=None): 

86 # 

87 # returns: 

88 # default = None: choices 

89 # default set: id of the default instance 

90 # 

91 

92 if default: 

93 item = models.ClassName.query.filter_by(name=default).first() 

94 if item: 

95 return str(item.id) 

96 return None 

97 

98 return [(f'{item.id}', item.name) for item in models.ClassName.query.order_by(func.lower(models.ClassName.name)).all()] 

99 

100def getBrowserTypes(default=None): 

101 # 

102 # returns: 

103 # default = None: choices 

104 # default set: id of the default instance 

105 # 

106 

107 if default: 

108 item = models.BrowserType.query.filter_by(name=default).first() 

109 if item: 

110 return str(item.id) 

111 return None 

112 

113 return [(f'{item.id}', item.name) for item in models.BrowserType.query.order_by(func.lower(models.BrowserType.name)).all()] 

114 

115def getTestCaseTypes(default=None): 

116 # 

117 # returns: 

118 # default = None: choices 

119 # default set: id of the default instance 

120 # 

121 

122 if default: 

123 item = models.TestCaseType.query.filter_by(name=default).first() 

124 if item: 

125 return str(item.id) 

126 return None 

127 

128 return [(f'{item.id}', item.name) for item in models.TestCaseType.query.order_by(func.lower(models.TestCaseType.name)).all()] 

129 

130def getActivityTypes(default=None): 

131 # 

132 # returns: 

133 # default = None: choices 

134 # default set: id of the default instance 

135 # 

136 

137 if default: 

138 item = models.ActivityType.query.filter_by(name=default).first() 

139 if item: 

140 return str(item.id) 

141 return None 

142 

143 return [(f'{item.id}', item.name) for item in models.ActivityType.query.order_by(func.lower(models.ActivityType.name)).all()] 

144 

145 

146def getLocatorTypes(default=None): 

147 # 

148 # returns: 

149 # default = None: choices 

150 # default set: id of the default instance 

151 # 

152 

153 if default: 

154 item = models.LocatorType.query.filter_by(name=default).first() 

155 if item: 

156 return str(item.id) 

157 return None 

158 

159 return [('0', 'none')] + \ 

160 [(str(item.id), item.name) for item in models.LocatorType.query.order_by(func.lower(models.LocatorType.name)).all()] 

161 

162 

163# 

164# Comaprisions 

165# 

166 

167COMPARISONS = [ 

168 'none', 

169 '=', 

170 '>', 

171 '<', 

172 '>=', 

173 '<=', 

174 '<>', 

175] 

176 

177def getComparisonChoices(): 

178 return list(map(lambda x: (str(x[0]), x[1]), enumerate(COMPARISONS))) 

179 

180def getComparisonId(option): 

181 if option: 

182 for index, sign in enumerate(COMPARISONS): 

183 if option == sign: 

184 return str(index) 

185 return '0' 

186 

187# 

188# Get Items By Name 

189# 

190 

191def getOrCreateClassNameByName(name, description): 

192 # get ClassName from DB 

193 classname = models.ClassName.query.filter_by(name=name).first() 

194 if classname is None: 

195 # create ClassName if it doesn't exist 

196 classname = models.ClassName( 

197 name=name, 

198 description=description, 

199 ) 

200 db.session.add(classname) 

201 db.session.commit() 

202 app.logger.info(f'Created ClassName ID #{classname.id} by {current_user}.') 

203 

204 return classname 

205 

206 

207def getBrowserTypeByName(name): 

208 # browser mapper 

209 bm = { 

210 'BROWSER_FIREFOX': "FF", 

211 'BROWSER_CHROME': "Chrome", 

212 'BROWSER_SAFARI': "Safari", 

213 'BROWSER_EDGE': "Edge", 

214 } 

215 if bm.get(name.split('.')[-1]): 

216 return models.BrowserType.query.filter_by(name=bm[name.split('.')[-1]]).first() 

217 return models.BrowserType.query.filter_by(name=name).first() 

218 

219def getTestCaseTypeByName(name): 

220 return models.TestCaseType.query.filter_by(name=name).first() 

221 

222def getActivityTypeByName(name): 

223 return models.ActivityType.query.filter(func.upper(models.ActivityType.name) == name.upper()).first() 

224 

225def getLocatorTypeByName(name): 

226 if name: 

227 #return models.LocatorType.query.filter(func.upper(models.LocatorType.name) == name.upper()).first() 

228 return models.LocatorType.query.filter_by(name=name).first() 

229 else: 

230 return None 

231 

232def getBooleanValue(value): 

233 if value: 

234 return True 

235 else: 

236 return False 

237 

238 

239# 

240# Cascade Delete 

241# 

242 

243def deleteCascade(item_type, item_id): 

244 # 

245 # implementation of cascade delete of items 

246 # 

247 

248 # delete Testrun and its children 

249 if item_type == 'testrun': 

250 item = models.Testrun.query.get(item_id) 

251 # delete children calls 

252 for child in item.calls: 

253 db.session.delete(child) 

254 app.logger.info(f'Deleted Testrun Call {child} by {current_user}.') 

255 db.session.commit() 

256 # delete children TestCaseSequences 

257 for child in item.testcase_sequences: 

258 deleteCascade('testcase_sequence', child.id) 

259 # delete Testrun 

260 db.session.delete(item) 

261 db.session.commit() 

262 app.logger.info(f'Deleted {item_type} {item.uuid} by {current_user}.') 

263 

264 # delete TestCaseSequence and its children 

265 elif item_type == 'testcase_sequence': 

266 item = models.TestCaseSequence.query.get(item_id) 

267 # check if item has not more then one parent 

268 if len(item.testrun) <= 1: 

269 # delete children DataFiles 

270 for child in item.datafiles: 

271 db.session.delete(child) # TODO: delete files at DataFile Service 

272 app.logger.info(f'Deleted DataFile {child.uuid} by {current_user}.') 

273 db.session.commit() 

274 # delete children TestCases 

275 for child in item.testcases: 

276 deleteCascade('testcase', child.id) 

277 # delete TestCaseSequence 

278 db.session.delete(item) 

279 db.session.commit() 

280 app.logger.info(f'Deleted {item_type} {item.uuid} by {current_user}.') 

281 

282 # delete TestCase and its children 

283 elif item_type == 'testcase': 

284 item = models.TestCase.query.get(item_id) 

285 # check if item has not more then one parent 

286 if len(item.testcase_sequence) <= 1: 

287 # delete children TestCaseSequences 

288 for child in item.teststep_sequences: 

289 deleteCascade('teststep_sequence', child.id) 

290 # delete TestCase 

291 db.session.delete(item) 

292 db.session.commit() 

293 app.logger.info(f'Deleted {item_type} {item.uuid} by {current_user}.') 

294 

295 # delete TestCaseSequence and its children 

296 elif item_type == 'teststep_sequence': 

297 item = models.TestStepSequence.query.get(item_id) 

298 # check if item has not more then one parent 

299 if len(item.testcase) <= 1: 

300 # delete children TestStepExecutions 

301 for child in item.teststeps: 

302 deleteCascade('teststep', child.id) 

303 # delete TestCaseSequence 

304 db.session.delete(item) 

305 db.session.commit() 

306 app.logger.info(f'Deleted {item_type} {item.uuid} by {current_user}.') 

307 

308 # delete TestStepExecution 

309 elif item_type == 'teststep': 

310 item = models.TestStepExecution.query.get(item_id) 

311 db.session.delete(item) 

312 db.session.commit() 

313 app.logger.info(f'Deleted {item_type} {item.uuid} by {current_user}.') 

314 

315 # invalid type 

316 else: 

317 raise Exception(f'Item type {item_type} does not exists.') 

318 

319 

320# 

321# Integrity check 

322# 

323 

324def idAsBytes(item_id): 

325 return uuid.UUID(item_id).bytes 

326 

327def getTestrunIfCorrect(testrun_id): 

328 # 

329 # checks if Testrun tree is completed 

330 # 

331 

332 # get testrun 

333 testrun = models.Testrun.query.get(idAsBytes(testrun_id)) 

334 if not testrun: 

335 raise Exception(f'Testrun {testrun_id} does not exist') 

336 

337 # get testrun status 

338 status, message = testrun.get_status() 

339 if not status: 

340 raise Exception(message) 

341 

342 return testrun 

343 

344 

345# 

346# Testrun export/import 

347# 

348 

349def exportJSON(testrun_id, shouldBeSaved=True): 

350 # 

351 # Export Testrun to JSON 

352 # 

353 

354 # get testrun 

355 testrun = getTestrunIfCorrect(testrun_id) 

356 testrun_json = testrun.to_json() 

357 

358 # save to file 

359 if shouldBeSaved: 

360 json_file = re.sub(r'\s+', '_', f'{testrun.name}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json') 

361 with open(os.path.join(app.root_path, 'static/files', json_file), 'w') as f: 

362 json.dump(testrun_json, f) 

363 

364 return json_file 

365 

366 # return dict 

367 else: 

368 return testrun_json 

369 

370 

371def exportXLSX(testrun_id): 

372 # 

373 # Exports Testrun to XLSX 

374 # 

375 

376 # get testrun 

377 testrun = getTestrunIfCorrect(testrun_id) 

378 

379 # create workbook 

380 headers = { 

381 'TestRun': [ 

382 'Attribute', 

383 'Value', 

384 ], 

385 

386 'TestCaseSequence': [ 

387 'Number', 

388 'SequenceClass', 

389 'TestDataFileName', 

390 'Sheetname', 

391 ], 

392 

393 'TestCase': [ 

394 'TestCaseSequenceNumber', 

395 'TestCaseNumber', 

396 'TestCaseClass', 

397 'TestCaseType', 

398 'Browser', 

399 ], 

400 

401 'TestStep': [ 

402 'TestCaseSequenceNumber', 

403 'TestCaseNumber', 

404 'TestStepNumber', 

405 'TestStepClass', 

406 ], 

407 

408 'TestStepExecution': [ 

409 'TestCaseSequenceNumber', 

410 'TestCaseNumber', 

411 'TestStepNumber', 

412 'TestStepExecutionNumber', 

413 'Activity', 

414 'LocatorType', 

415 'Locator', 

416 'Value', 

417 'Comparison', 

418 'Value2', 

419 'Timeout', 

420 'Optional', 

421 'Release', 

422 ], 

423 } 

424 

425 # create workbook 

426 xlsx_file = re.sub(r'\s+', '_', f'{testrun.name}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx') 

427 workbook = xlsxwriter.Workbook(os.path.join(app.root_path, 'static/files', xlsx_file)) 

428 header_format = workbook.add_format({'bold': True}) 

429 

430 # create sheets with headers 

431 worksheets = {} 

432 for sheet, items in headers.items(): 

433 worksheets[sheet] = workbook.add_worksheet(sheet) 

434 # headers 

435 for col, value in enumerate(items): 

436 # set header 

437 worksheets[sheet].write(0, col, value, header_format) 

438 # set width 

439 worksheets[sheet].set_column(col, col, 1.1*len(value)) 

440 

441 # write data 

442 # to TestRun 

443 worksheets['TestRun'].write(1, 0, 'Export Format') 

444 worksheets['TestRun'].write(1, 1, 'XLSX') 

445 

446 # child indexes 

447 tc_index, tss_index, ts_index = 0, 0, 0 

448 

449 # to TestCaseSequence 

450 for tcs_index, tcs in enumerate(testrun.testcase_sequences, 1): 

451 worksheets['TestCaseSequence'].write(tcs_index, 0, tcs_index) 

452 worksheets['TestCaseSequence'].write(tcs_index, 1, tcs.classname.name) 

453 worksheets['TestCaseSequence'].write(tcs_index, 2, tcs.datafiles[0].filename) 

454 worksheets['TestCaseSequence'].write(tcs_index, 3, tcs.datafiles[0].sheet) 

455 

456 # to TestCase 

457 for tc in tcs.testcases: 

458 tc_index += 1 

459 worksheets['TestCase'].write(tc_index, 0, tcs_index) 

460 worksheets['TestCase'].write(tc_index, 1, tc_index) 

461 worksheets['TestCase'].write(tc_index, 2, tc.classname.name) 

462 worksheets['TestCase'].write(tc_index, 3, tc.testcase_type.name) 

463 worksheets['TestCase'].write(tc_index, 4, tc.browser_type.name) 

464 

465 # to TestStep 

466 for tss in tc.teststep_sequences: 

467 tss_index += 1 

468 worksheets['TestStep'].write(tss_index, 0, tcs_index) 

469 worksheets['TestStep'].write(tss_index, 1, tc_index) 

470 worksheets['TestStep'].write(tss_index, 2, tss_index) 

471 worksheets['TestStep'].write(tss_index, 3, tss.classname.name) 

472 

473 # to TestStepExecution 

474 for ts in tss.teststeps: 

475 ts_index += 1 

476 worksheets['TestStepExecution'].write(ts_index, 0, tcs_index) 

477 worksheets['TestStepExecution'].write(ts_index, 1, tc_index) 

478 worksheets['TestStepExecution'].write(ts_index, 2, tss_index) 

479 worksheets['TestStepExecution'].write(ts_index, 3, ts_index) 

480 worksheets['TestStepExecution'].write(ts_index, 4, ts.activity_type.name) 

481 # LocatorType name 

482 if ts.locator_type: 

483 worksheets['TestStepExecution'].write(ts_index, 5, ts.locator_type.name) 

484 

485 worksheets['TestStepExecution'].write(ts_index, 6, ts.locator) 

486 worksheets['TestStepExecution'].write(ts_index, 7, ts.value) 

487 worksheets['TestStepExecution'].write(ts_index, 8, ts.comparison) 

488 worksheets['TestStepExecution'].write(ts_index, 9, ts.value2) 

489 worksheets['TestStepExecution'].write(ts_index, 10, ts.timeout) 

490 worksheets['TestStepExecution'].write(ts_index, 11, 'X' if ts.optional else None) 

491 worksheets['TestStepExecution'].write(ts_index, 12, ts.release) 

492 

493 workbook.close() 

494 return xlsx_file 

495 

496def uploadDataFile(file, fileId=None): 

497 # 

498 # upload DataFile to file-service 

499 # 

500 

501 # call DataFile Service 

502 if fileId: 

503 # update 

504 url = '/'.join(( 

505 'http:/', 

506 app.config.get('BAANGT_DATAFILE_HOST'), 

507 app.config.get('BAANGT_DATAFILE_UPDATE'), 

508 fileId, 

509 )) 

510 else: 

511 # create 

512 url = '/'.join(( 

513 'http:/', 

514 app.config.get('BAANGT_DATAFILE_HOST'), 

515 app.config.get('BAANGT_DATAFILE_SAVE'), 

516 )) 

517 app.logger.info(f'Call {url} to save file {file.filename} by {current_user}.') 

518 

519 r = requests.post(url, files={'dataFile': file}) 

520 app.logger.info(f'Response to {current_user}: {r.status_code}\n{r.text}') 

521 return r 

522 

523def importDataFile(xlsx_file, datafile_id=None): 

524 # 

525 # imports XLSX DataFile 

526 # 

527 

528 # open xlsx 

529 try: 

530 xl = xlrd.open_workbook(file_contents=xlsx_file.read()) 

531 except xlrd.XLRDError: 

532 raise Exception(f'File "{xlsx_file.filename}" could not be uploaded.') 

533 

534 # get data sheet name 

535 sheet_name = 'data' if 'data' in xl.sheet_names() else xl.sheet_names()[0] 

536 

537 # upload data file 

538 xlsx_file.seek(0) 

539 r = uploadDataFile(file=xlsx_file, fileId=datafile_id) 

540 

541 # get response 

542 if r.status_code == 200: 

543 # datafile update 

544 if datafile_id: 

545 datafile = models.DataFile.query.get(uuid.UUID(datafile_id).bytes) 

546 if datafile: 

547 datafile.filename = xlsx_file.filename 

548 datafile.created = datetime.utcnow() 

549 datafile.creator = current_user 

550 db.session.commit() 

551 app.logger.info(f'Updated DataFile {datafile_id} by {current_user}.') 

552 

553 return datafile_id 

554 

555 # datafile create 

556 file_id = json.loads(r.text).get('uuid') 

557 datafile = models.DataFile( 

558 id=uuid.UUID(file_id).bytes, 

559 filename=xlsx_file.filename, 

560 sheet=sheet_name, 

561 creator=current_user, 

562 ) 

563 db.session.add(datafile) 

564 db.session.commit() 

565 app.logger.info(f'Created DataFile {file_id} by {current_user}.') 

566 else: 

567 app.logger.error(f'Failed upload Data File "{name}" by {current_user}. {r.text}') 

568 raise Exception(f'Failed upload Data File "{name}". {r.text}', 'warning') 

569 

570 return file_id 

571 

572def importXLSX(xlsx_file, datafiles=None, item_id=None): 

573 # 

574 # imports testrun from xlsx file 

575 # 

576 

577 # open xlsx 

578 try: 

579 xl = xlrd.open_workbook(file_contents=xlsx_file.read()) 

580 except xlrd.XLRDError: 

581 raise Exception(f'File "{xlsx_file.filename}" could not be imporeted.') 

582 # get file name 

583 file_name = os.path.basename(xlsx_file.filename) 

584 

585 # check for import option: create or update 

586 testrun = None 

587 if item_id is None: 

588 # check if imported Testrun already exists 

589 testrun = models.Testrun.query.filter_by(name=file_name).first() 

590 if testrun is None: 

591 # create mode 

592 app.logger.info(f'Importing a Testrun from {xlsx_file.filename} by {current_user}.') 

593 else: 

594 # update mode 

595 item_id = testrun.id 

596 else: 

597 # get item 

598 testrun = models.Testrun.query.get(item_id) 

599 if testrun is None: # item does not exist 

600 raise Exception(f'Testrun {uuid.UUID(bytes=item_id)} does not exists.') 

601 

602 if testrun: 

603 app.logger.info(f'Updating Testrun {testrun.uuid} from {xlsx_file.filename} by {current_user}.') 

604 

605 # Testrun object 

606 if testrun is None: 

607 # create Testrun 

608 testrun = models.Testrun( 

609 name=file_name, 

610 description=f'Imported from "{file_name}"', 

611 creator=current_user, 

612 ) 

613 db.session.add(testrun) 

614 else: 

615 # update Testrun 

616 testrun.description = f'Updated from "{file_name}"' 

617 testrun.editor = current_user 

618 testrun.edited = datetime.utcnow() 

619 

620 # TestCaseSequence objects 

621 if 'TestCaseSequence' in xl.sheet_names(): 

622 # get sheet 

623 testcase_sequence_sheet = xl.sheet_by_name('TestCaseSequence') 

624 # get headers as dict 

625 headers = {h.lower(): i for i, h in enumerate(testcase_sequence_sheet.row_values(0))} 

626 # get TestCaseSequences 

627 for row in range(1, testcase_sequence_sheet.nrows): 

628 # TCS number 

629 n = int(testcase_sequence_sheet.cell(row, headers['number']).value) - 1 \ 

630 if ('number' in headers) else 0 

631 # ClassName 

632 name = testcase_sequence_sheet.cell(row, headers['sequenceclass']).value \ 

633 if ('sequenceclass' in headers) else CLASSNAME_TESTCASESEQUENCE 

634 classname = getOrCreateClassNameByName(name, f'Imported from "{file_name}"') 

635 

636 # DataFile 

637 datafile = None 

638 if headers.get('TestDataFileName'): 

639 # get DataFile name from sheet 

640 name = testcase_sequence_sheet.cell(row, headers['TestDataFileName']).value 

641 # check if datafile uploaded 

642 posted_datafile = None 

643 for df in datafiles: 

644 if df.data and df.data.filename == name: 

645 posted_datafile = df.data 

646 if posted_datafile: 

647 # upload Data File to File-Service 

648 try: 

649 if n < len(testrun.testcase_sequences) and testrun.testcase_sequences[n].datafiles: 

650 # update 

651 r = uploadDataFile( 

652 file=posted_datafile, 

653 fileId=testrun.testcase_sequences[n].datafiles[0].uuid, 

654 ) 

655 else: 

656 # create 

657 r = uploadDataFile(file=posted_datafile) 

658 

659 # get response 

660 if r.status_code == 200: 

661 file_id = json.loads(r.text).get('uuid') 

662 file_id_bytes = uuid.UUID(file_id).bytes 

663 datafile = models.DataFile.query.get(file_id_bytes) 

664 if datafile is None: 

665 # create DataFile 

666 datafile = models.DataFile( 

667 id=file_id_bytes, 

668 filename=file_name, 

669 sheet='Testcases', 

670 creator=current_user, 

671 ) 

672 db.session.add(datafile) 

673 else: 

674 flash(f'WARNING: Failed upload Data File "{name}" for Test Case Sequence {n}. {r.text}', 'warning') 

675 app.logger.warning(f'Failed upload Data File "{name}" for Testrun ID {testrun.uuid} by {current_user}. {r.text}') 

676 

677 except Exception as e: 

678 # failed to connect 

679 flash(f'WARNING: Failed upload Data File "{name}": {e}', 'warning') 

680 app.logger.warning(f'Failed upload Data File "{name}" for Testrun ID {testrun.uuid} by {current_user}. {e}') 

681 

682 else: 

683 flash(f'WARNING: Data File "{name}" not uploaded for Test Case Sequence {n}.', 'warning') 

684 app.logger.warning(f'Data File "{name}" not uploaded for Testrun ID {testrun.uuid} by {current_user}.') 

685 

686 else: 

687 flash(f'WARNING: Data File not defined for Test Case Sequence {n}.', 'warning') 

688 app.logger.warning(f'Data File not defined for Testrun ID {testrun.uuid} by {current_user}.') 

689 

690 # TestCaseSequence 

691 if n < len(testrun.testcase_sequences): 

692 # update item 

693 testrun.testcase_sequences[n].description = f'Updated from "{file_name}"' 

694 testrun.testcase_sequences[n].editor = current_user 

695 testrun.testcase_sequences[n].edited = datetime.utcnow() 

696 testrun.testcase_sequences[n].classname = classname 

697 if datafile: 

698 testrun.testcase_sequences[n-1].datafiles = [datafile] 

699 else: 

700 # create item 

701 testcase_sequence = models.TestCaseSequence( 

702 name=f'{file_name}_{row}', 

703 description=f'Imported from "{file_name}"', 

704 creator=current_user, 

705 classname=classname, 

706 datafiles=[datafile] if datafile else [], 

707 testrun=[testrun], 

708 ) 

709 db.session.add(testcase_sequence) 

710 

711 else: 

712 # create default TestCaseSequence 

713 

714 # ClassName 

715 # get ClassName from DB or create if it doesn't exist 

716 classname = getOrCreateClassNameByName(CLASSNAME_TESTCASESEQUENCE, 'Default for TestCaseSequence.') 

717 

718 # DataFiles 

719 datafile = None 

720 if 'data' in xl.sheet_names(): 

721 # call DataFile Service 

722 xlsx_file.seek(0) 

723 try: 

724 if testrun.testcase_sequences and testrun.testcase_sequences[0].datafiles: 

725 # update 

726 r = uploadDataFile( 

727 file=xlsx_file, 

728 fileId=testrun.testcase_sequences[0].datafiles[0].uuid, 

729 ) 

730 else: 

731 # create 

732 r = uploadDataFile(file=xlsx_file) 

733 

734 # get response 

735 if r.status_code == 200: 

736 file_id = json.loads(r.text).get('uuid') 

737 file_id_bytes = uuid.UUID(file_id).bytes 

738 datafile = models.DataFile.query.get(file_id_bytes) 

739 if datafile is None: 

740 # create DataFile 

741 datafile = models.DataFile( 

742 id=file_id_bytes, 

743 filename=file_name, 

744 creator=current_user, 

745 ) 

746 db.session.add(datafile) 

747 except Exception as e: 

748 # failed to connect 

749 flash(f'WARNING: Failed upload Data File "{xlsx_file.filename}": {e}', 'warning') 

750 app.logger.warning(f'Failed upload Data File "{xlsx_file.filename}" for Testrun ID {testrun.uuid} by {current_user}. {e}') 

751 

752 else: 

753 flash(f'WARNING: Uploaded file does not define Data.', 'warning') 

754 app.logger.warning(f'Uploaded file does not define Data for Testrun {testrun.uuid} by {current_user}.') 

755 

756 # TestCaseSequence 

757 if testrun.testcase_sequences: 

758 # update item 

759 testrun.testcase_sequences[0].description = f'Updated to default.' 

760 testrun.testcase_sequences[0].editor = current_user 

761 testrun.testcase_sequences[0].edited = datetime.utcnow() 

762 testrun.testcase_sequences[0].classname = classname 

763 if datafile: 

764 testrun.testcase_sequences[0].datafiles = [datafile] 

765 

766 else: 

767 # create item 

768 testcase_sequence = models.TestCaseSequence( 

769 name=f'{file_name}', 

770 description=f'Default for "{file_name}"', 

771 creator=current_user, 

772 classname=classname, 

773 datafiles=[datafile] if datafile else [], 

774 testrun=[testrun], 

775 ) 

776 db.session.add(testcase_sequence) 

777 

778 # TestCase objects 

779 if 'TestCase' in xl.sheet_names(): 

780 # get sheet 

781 testcase_sheet = xl.sheet_by_name('TestCase') 

782 # get headers as dict 

783 headers = {h[1]: h[0] for h in enumerate(testcase_sheet.row_values(0))} 

784 # get TestCases 

785 for row in range(1, testcase_sheet.nrows): 

786 # get TestCaseSequenceNumber 

787 if headers.get('TestCaseSequenceNumber') is None: 

788 # default number is 0 

789 i = 0 

790 else: 

791 # get the number from sheet 

792 i = int(testcase_sheet.cell(row, headers['TestCaseSequenceNumber']).value) -1 

793 

794 # get TestCaseNumber 

795 if headers.get('TestCaseNumber') is None: 

796 # default number is 0 

797 n = 0 

798 else: 

799 # get the number from sheet 

800 n = int(testcase_sheet.cell(row, headers['TestCaseNumber']).value) - 1 

801 

802 # ClassName 

803 if headers.get('TestCaseClass') is None: 

804 # default ClassName name 

805 name = CLASSNAME_TESTCASE 

806 else: 

807 # get ClassName name from sheet 

808 name = testcase_sheet.cell(row, headers['TestCaseClass']).value 

809 # get ClassName from DB or create if it doesn't exist 

810 classname = getOrCreateClassNameByName(name, f'Imported from "{file_name}"') 

811 

812 # TestCase 

813 # Browser Type 

814 if headers.get('Browser') is None: 

815 raise Exception('Sheet "TestCase" does not contain "Browser" column.') 

816 else: 

817 name = testcase_sheet.cell(row, headers['Browser']).value 

818 browser_type = getBrowserTypeByName(name) 

819 if browser_type is None: 

820 raise Exception(f'Unknown browser type "{name}": sheet "TestCase", row {row+1}.') 

821 # TestCase Type 

822 if headers.get('TestCaseType') is None: 

823 raise Exception('Sheet "TestCase" does not contain "TestCaseType" column.') 

824 else: 

825 name = testcase_sheet.cell(row, headers['TestCaseType']).value 

826 testcase_type=getTestCaseTypeByName(name) 

827 if testcase_type is None: 

828 raise Exception(f'Unknown testcase type "{name}": sheet "TestCase" row {row+1}.') 

829 

830 if n < len(testrun.testcase_sequences[i].testcases): 

831 # update item 

832 testrun.testcase_sequences[i].testcases[n].description = f'Updated from "{file_name}"' 

833 testrun.testcase_sequences[i].testcases[n].editor = current_user 

834 testrun.testcase_sequences[i].testcases[n].edited = datetime.utcnow() 

835 testrun.testcase_sequences[i].testcases[n].classname = classname 

836 testrun.testcase_sequences[i].testcases[n].browser_type = browser_type 

837 testrun.testcase_sequences[i].testcases[n].testcase_type = testcase_type 

838 else: 

839 # create item 

840 testcase = models.TestCase( 

841 name=f'{file_name}_{row}', 

842 description=f'Imported from "{file_name}"', 

843 creator=current_user, 

844 classname=classname, 

845 browser_type=browser_type, 

846 testcase_type=testcase_type, 

847 testcase_sequence=[testrun.testcase_sequences[i]], 

848 ) 

849 db.session.add(testcase) 

850 else: 

851 # create default TestCase 

852 

853 # ClassName 

854 # get ClassName from DB or create if it doesn't exist 

855 classname = getOrCreateClassNameByName(CLASSNAME_TESTCASE, 'Default for TestCase.') 

856 

857 # TestCase 

858 if testrun.testcase_sequences[0].testcases: 

859 # update item 

860 testrun.testcase_sequences[0].testcases[0].description = f'Updated to default.' 

861 testrun.testcase_sequences[0].testcases[0].editor = current_user 

862 testrun.testcase_sequences[0].testcases[0].edited = datetime.utcnow() 

863 testrun.testcase_sequences[0].testcases[0].classname = classname 

864 testrun.testcase_sequences[0].testcases[0].browser_type = getBrowserTypeByName(BROWSER_TYPE) 

865 testrun.testcase_sequences[0].testcases[0].testcase_type = getTestCaseTypeByName(TESTCASE_TYPE) 

866 else: 

867 # create item 

868 testcase = models.TestCase( 

869 name=f'{file_name}', 

870 description=f'Default for "{file_name}"', 

871 creator=current_user, 

872 classname=classname, 

873 browser_type=getBrowserTypeByName(BROWSER_TYPE), 

874 testcase_type=getTestCaseTypeByName(TESTCASE_TYPE), 

875 testcase_sequence=[testrun.testcase_sequences[0]], 

876 ) 

877 db.session.add(testcase) 

878 

879 # TestStepSequence objects 

880 if 'TestStep' in xl.sheet_names(): 

881 # get sheet 

882 teststep_sheet = xl.sheet_by_name('TestStep') 

883 # get headers as dict 

884 headers = {h[1]: h[0] for h in enumerate(teststep_sheet.row_values(0))} 

885 # get TestSteps 

886 print('*** TSS') 

887 for row in range(1, teststep_sheet.nrows): 

888 # get TestStepNumber 

889 n = 0 # default number is 0  

890 if 'TestStepNumber' in headers: 

891 # get the number from sheet 

892 n = int(teststep_sheet.cell(row, headers['TestStepNumber']).value) - 1 

893 

894 # get TestCaseNumber 

895 j = 0 # default number is 0  

896 if 'TestCaseNumber' in headers: 

897 # get the number from sheet 

898 j = int(teststep_sheet.cell(row, headers['TestCaseNumber']).value) - 1 

899 

900 # get TestCaseSequenceNumber 

901 i = 0 # default number is 0  

902 if 'TestCaseSequenceNumber' in headers: 

903 # get the number from sheet 

904 i = int(teststep_sheet.cell(row, headers['TestCaseSequenceNumber']).value) - 1 

905 

906 # ClassName 

907 name = CLASSNAME_TESTSTEP # default ClassName name 

908 if 'TestStepClass' in headers: 

909 # get ClassName name from sheet 

910 name = teststep_sheet.cell(row, headers['TestStepClass']).value 

911 # get ClassName from DB or create if it doesn't exist 

912 classname = getOrCreateClassNameByName(name, f'Imported from "{file_name}"') 

913 print(f'{row}\t{i}\t{j}\t{n}\t{name}') 

914 print(f'DB\t{len(testrun.testcase_sequences)}\t{len(testrun.testcase_sequences[i].testcases)}\t{len(testrun.testcase_sequences[i].testcases[j].teststep_sequences)}') 

915 

916 if n < len(testrun.testcase_sequences[i].testcases[j].teststep_sequences): 

917 # update item 

918 testrun.testcase_sequences[i].testcases[j].teststep_sequences[n].description = f'Updated from "{file_name}"' 

919 testrun.testcase_sequences[i].testcases[j].teststep_sequences[n].editor = current_user 

920 testrun.testcase_sequences[i].testcases[j].teststep_sequences[n].edited = datetime.utcnow() 

921 testrun.testcase_sequences[i].testcases[j].teststep_sequences[n].classname = classname 

922 else: 

923 # create item 

924 teststep_sequence = models.TestStepSequence( 

925 name=f'{file_name}_{row}', 

926 description=f'Imported from "{file_name}"', 

927 creator=current_user, 

928 classname=classname, 

929 testcase=[testrun.testcase_sequences[i].testcases[j]], 

930 ) 

931 db.session.add(teststep_sequence) 

932 else: 

933 # create default TestStep 

934 # ClassName 

935 # get ClassName from DB or create if it doesn't exist 

936 classname = getOrCreateClassNameByName(CLASSNAME_TESTSTEP, 'Default for TestStep') 

937 

938 # TestStepSequence 

939 if testrun.testcase_sequences[0].testcases[0].teststep_sequences: 

940 # update item 

941 testrun.testcase_sequences[0].testcases[0].teststep_sequences[0].description = f'Updated to default.' 

942 testrun.testcase_sequences[0].testcases[0].teststep_sequences[0].editor = current_user 

943 testrun.testcase_sequences[0].testcases[0].teststep_sequences[0].edited = datetime.utcnow() 

944 testrun.testcase_sequences[0].testcases[0].teststep_sequences[0].classname = classname 

945 else: 

946 # create item 

947 teststep_sequance = models.TestStepSequence( 

948 name=f'{file_name}_1', 

949 description=f'Default for "{file_name}"', 

950 creator=current_user, 

951 classname=classname, 

952 testcase=[testrun.testcase_sequences[0].testcases[0]] 

953 ) 

954 db.session.add(teststep_sequance) 

955 

956 # TestStepsExecution objects 

957 if 'TestStepExecution' in xl.sheet_names(): 

958 # get sheet 

959 teststep_execution_sheet = xl.sheet_by_name('TestStepExecution') 

960 # get headers as dict 

961 headers = {h[1]: h[0] for h in enumerate(teststep_execution_sheet.row_values(0))} 

962 # get TestStepExecutions 

963 print('*** TS ROWS:') 

964 print(headers) 

965 for row in range(1, teststep_execution_sheet.nrows): 

966 # get TestStepExecutionNumber 

967 n = row - 1 # default number 

968 if 'TestStepExecutionNumber' in headers: 

969 # get the number from sheet 

970 n = int(teststep_execution_sheet.cell(row, headers['TestStepExecutionNumber']).value) - 1 

971 

972 # get TestStepNumber 

973 k = 0 

974 if 'TestStepNumber' in headers: 

975 # get the number from sheet 

976 k = int(teststep_execution_sheet.cell(row, headers['TestStepNumber']).value) - 1 

977 

978 # Activity Type 

979 if headers.get('Activity') is None: 

980 raise Exception('Sheet "TestStepExecution" does not contain "Activity" column.') 

981 else: 

982 name = teststep_execution_sheet.cell(row, headers['Activity']).value 

983 activity_type = getActivityTypeByName(name) 

984 if activity_type is None: 

985 raise Exception(f'Unknown activity type "{name}": sheet "TestStepExecution", row {row+1}') 

986 

987 # Locator Type 

988 if headers.get('LocatorType') is None: 

989 raise Exception('Sheet "TestStepExecution" does not contain "LocatorType" column.') 

990 else: 

991 locator_type = getLocatorTypeByName(teststep_execution_sheet.cell(row, headers['LocatorType']).value) 

992 

993 # get Locator 

994 locator = None 

995 if 'Locator' in headers: 

996 locator = teststep_execution_sheet.cell(row, headers['Locator']).value or None 

997 

998 # get Value 

999 value = None 

1000 if 'Value' in headers: 

1001 value = teststep_execution_sheet.cell(row, headers['Value']).value or None 

1002 if re.match(r'\d+\.0+', str(value)): 

1003 value = int(value) 

1004 

1005 # get Value 2 

1006 value2 = None 

1007 if 'Value2' in headers: 

1008 value2 = teststep_execution_sheet.cell(row, headers['Value2']).value or None 

1009 if re.match(r'\d+\.0+', str(value2)): 

1010 value2 = int(value2) 

1011 

1012 # get Comparison 

1013 comparison = None 

1014 if 'Comparison' in headers: 

1015 comparison = teststep_execution_sheet.cell(row, headers['Comparison']).value or None 

1016 

1017 # get Timeout 

1018 timeout = None 

1019 if 'Timeout' in headers: 

1020 timeout = teststep_execution_sheet.cell(row, headers['Timeout']).value or None 

1021 

1022 # get Optional 

1023 optional = False 

1024 if 'Optional' in headers: 

1025 optional = getBooleanValue(teststep_execution_sheet.cell(row, headers['Optional']).value) 

1026 

1027 # get Release 

1028 release = None 

1029 if 'Release' in headers: 

1030 release = teststep_execution_sheet.cell(row, headers['Release']).value or None 

1031 print(f'{row}\t{i}\t{j}\t{n}\t{name}') 

1032 

1033 if n < len(testrun.testcase_sequences[0].testcases[0].teststep_sequences[k].teststeps): 

1034 # update item 

1035 teststep = testrun.testcase_sequences[0].testcases[0].teststep_sequences[k].teststeps[n] 

1036 teststep.description = f'Updated from "{file_name}"' 

1037 teststep.editor = current_user 

1038 teststep.edited = datetime.utcnow() 

1039 teststep.activity_type=activity_type 

1040 teststep.locator_type=locator_type 

1041 teststep.locator=locator 

1042 teststep.value=value 

1043 teststep.comparison=comparison 

1044 teststep.value2=value2 

1045 teststep.timeout=timeout 

1046 teststep.optional=optional 

1047 teststep.release=release 

1048 else: 

1049 # create item 

1050 teststep = models.TestStepExecution( 

1051 name=f'{file_name}_{row}', 

1052 description=f'Imported from "{file_name}"', 

1053 creator=current_user, 

1054 teststep_sequence=testrun.testcase_sequences[0].testcases[0].teststep_sequences[k], 

1055 activity_type=activity_type, 

1056 locator_type=locator_type, 

1057 locator=locator, 

1058 value=value, 

1059 comparison=comparison, 

1060 value2=value2, 

1061 timeout=timeout, 

1062 optional=optional, 

1063 release=release, 

1064 ) 

1065 db.session.add(teststep) 

1066 

1067 # commit and log changes 

1068 db.session.commit() 

1069 item_types = getItemCategories().get('main') 

1070 log_import(item_types[0], testrun.uuid, testrun.edited) 

1071 for tcs in testrun.testcase_sequences: 

1072 log_import(item_types[1], tcs.uuid, tcs.edited) 

1073 for tc in tcs.testcases: 

1074 log_import(item_types[2], tc.uuid, tc.edited) 

1075 for tss in tc.teststep_sequences: 

1076 log_import(item_types[3], tss.uuid, tss.edited) 

1077 for ts in tss.teststeps: 

1078 log_import(item_types[4], ts.uuid, ts.edited) 

1079 

1080 

1081def log_import(item, item_id, updated): 

1082 action = 'Updated' if updated else 'Created' 

1083 app.logger.info(f'{action} {getItemType(item)} {item_id} by {current_user}.') 

1084 

1085 

1086# 

1087# Updating Testrun calls 

1088# 

1089def update_all_calls(): 

1090 for call in models.TestRunCall.query.filter_by(is_finished=False).filter_by(is_failed=False).all(): 

1091 update_call(call) 

1092 

1093def update_call(call): 

1094 # call API 

1095 url = '/'.join(( 

1096 'http:/', 

1097 app.config.get('BAANGT_API_HOST'), 

1098 app.config.get('BAANGT_API_STATUS'), 

1099 str(call), 

1100 )) 

1101 app.logger.info(f'Call results by {current_user}. API URL: {url}') 

1102 r = requests.get(url) 

1103 app.logger.info(f'Response: {r.status_code}') 

1104 

1105 if r.status_code == 500: 

1106 raise Exception('API Server interanal error') 

1107 

1108 elif r.status_code == 200: 

1109 results = json.loads(r.text) 

1110 call.is_finished = True 

1111 call.testrecords = results['Summary'].get('TestRecords') 

1112 call.successful = results['Summary'].get('Successful') 

1113 call.error = results['Summary'].get('Error') 

1114 call.paused = results['Summary'].get('Paused') 

1115 call.stage = results['GlobalSettings'].get('Stage') 

1116 # calculate duration in ms 

1117 starttime = datetime.strptime(results['Summary'].get('StartTime'), '%H:%M:%S') 

1118 endtime = datetime.strptime(results['Summary'].get('EndTime'), '%H:%M:%S') 

1119 duration = endtime - starttime 

1120 call.duration = duration.seconds + round(duration.microseconds/1000000) 

1121 # commit update 

1122 db.session.commit() 

1123 app.logger.info(f'Testrun call {call} updated by {current_user}') 

1124 

1125 elif r.status_code//100 == 4: 

1126 call.is_failed = True 

1127 call.error_message = r.text[-512:] 

1128 # commit update 

1129 db.session.commit() 

1130 app.logger.warning(f'Testrun call {call} updated by {current_user} as failed:\n{r.text}') 

1131