Coverage for app/utils/__init__.py : 44%

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
12#
13# Default VALUES
14#
15CLASSNAME_TESTCASESEQUENCE = 'GC.CLASSES_TESTCASESEQUENCE'
16CLASSNAME_TESTCASE = 'GC.CLASSES_TESTCASE'
17CLASSNAME_TESTSTEP = 'GC.CLASSES_TESTSTEPMASTER'
19BROWSER_TYPE = 'FF'
20TESTCASE_TYPE = 'Browser'
21ACTIVITY_TYPE = 'GOTOURL'
22#LOCATOR_TYPE = 'xpath'
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 ]
38 return categories
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 ''
59 # check for plurals
60 if plural:
61 name += 's'
63 return name
65#
66# generate choices of items
67#
69def getTestCaseSequences():
70 return [(f'{item.uuid}', item.name) for item in models.TestCaseSequence.query.order_by(func.lower(models.TestCaseSequence.name)).all()]
72def getDataFiles():
73 return [(f'{item.uuid}', item.filename) for item in models.DataFile.query.order_by(func.lower(models.DataFile.filename)).all()]
75def getTestCases():
76 return [(f'{item.uuid}', item.name) for item in models.TestCase.query.order_by(func.lower(models.TestCase.name)).all()]
78def getTestStepSequences():
79 return [(f'{item.uuid}', item.name) for item in models.TestStepSequence.query.order_by(func.lower(models.TestStepSequence.name)).all()]
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]
85def getClassNames(default=None):
86 #
87 # returns:
88 # default = None: choices
89 # default set: id of the default instance
90 #
92 if default:
93 item = models.ClassName.query.filter_by(name=default).first()
94 if item:
95 return str(item.id)
96 return None
98 return [(f'{item.id}', item.name) for item in models.ClassName.query.order_by(func.lower(models.ClassName.name)).all()]
100def getBrowserTypes(default=None):
101 #
102 # returns:
103 # default = None: choices
104 # default set: id of the default instance
105 #
107 if default:
108 item = models.BrowserType.query.filter_by(name=default).first()
109 if item:
110 return str(item.id)
111 return None
113 return [(f'{item.id}', item.name) for item in models.BrowserType.query.order_by(func.lower(models.BrowserType.name)).all()]
115def getTestCaseTypes(default=None):
116 #
117 # returns:
118 # default = None: choices
119 # default set: id of the default instance
120 #
122 if default:
123 item = models.TestCaseType.query.filter_by(name=default).first()
124 if item:
125 return str(item.id)
126 return None
128 return [(f'{item.id}', item.name) for item in models.TestCaseType.query.order_by(func.lower(models.TestCaseType.name)).all()]
130def getActivityTypes(default=None):
131 #
132 # returns:
133 # default = None: choices
134 # default set: id of the default instance
135 #
137 if default:
138 item = models.ActivityType.query.filter_by(name=default).first()
139 if item:
140 return str(item.id)
141 return None
143 return [(f'{item.id}', item.name) for item in models.ActivityType.query.order_by(func.lower(models.ActivityType.name)).all()]
146def getLocatorTypes(default=None):
147 #
148 # returns:
149 # default = None: choices
150 # default set: id of the default instance
151 #
153 if default:
154 item = models.LocatorType.query.filter_by(name=default).first()
155 if item:
156 return str(item.id)
157 return None
159 return [('0', 'none')] + \
160 [(str(item.id), item.name) for item in models.LocatorType.query.order_by(func.lower(models.LocatorType.name)).all()]
163#
164# Comaprisions
165#
167COMPARISONS = [
168 'none',
169 '=',
170 '>',
171 '<',
172 '>=',
173 '<=',
174 '<>',
175]
177def getComparisonChoices():
178 return list(map(lambda x: (str(x[0]), x[1]), enumerate(COMPARISONS)))
180def getComparisonId(option):
181 if option:
182 for index, sign in enumerate(COMPARISONS):
183 if option == sign:
184 return str(index)
185 return '0'
187#
188# Get Items By Name
189#
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}.')
204 return classname
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()
219def getTestCaseTypeByName(name):
220 return models.TestCaseType.query.filter_by(name=name).first()
222def getActivityTypeByName(name):
223 return models.ActivityType.query.filter(func.upper(models.ActivityType.name) == name.upper()).first()
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
232def getBooleanValue(value):
233 if value:
234 return True
235 else:
236 return False
239#
240# Cascade Delete
241#
243def deleteCascade(item_type, item_id):
244 #
245 # implementation of cascade delete of items
246 #
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}.')
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}.')
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}.')
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}.')
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}.')
315 # invalid type
316 else:
317 raise Exception(f'Item type {item_type} does not exists.')
320#
321# Integrity check
322#
324def idAsBytes(item_id):
325 return uuid.UUID(item_id).bytes
327def getTestrunIfCorrect(testrun_id):
328 #
329 # checks if Testrun tree is completed
330 #
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')
337 # get testrun status
338 status, message = testrun.get_status()
339 if not status:
340 raise Exception(message)
342 return testrun
345#
346# Testrun export/import
347#
349def exportJSON(testrun_id, shouldBeSaved=True):
350 #
351 # Export Testrun to JSON
352 #
354 # get testrun
355 testrun = getTestrunIfCorrect(testrun_id)
356 testrun_json = testrun.to_json()
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)
364 return json_file
366 # return dict
367 else:
368 return testrun_json
371def exportXLSX(testrun_id):
372 #
373 # Exports Testrun to XLSX
374 #
376 # get testrun
377 testrun = getTestrunIfCorrect(testrun_id)
379 # create workbook
380 headers = {
381 'TestRun': [
382 'Attribute',
383 'Value',
384 ],
386 'TestCaseSequence': [
387 'Number',
388 'SequenceClass',
389 'TestDataFileName',
390 'Sheetname',
391 ],
393 'TestCase': [
394 'TestCaseSequenceNumber',
395 'TestCaseNumber',
396 'TestCaseClass',
397 'TestCaseType',
398 'Browser',
399 ],
401 'TestStep': [
402 'TestCaseSequenceNumber',
403 'TestCaseNumber',
404 'TestStepNumber',
405 'TestStepClass',
406 ],
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 }
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})
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))
441 # write data
442 # to TestRun
443 worksheets['TestRun'].write(1, 0, 'Export Format')
444 worksheets['TestRun'].write(1, 1, 'XLSX')
446 # child indexes
447 tc_index, tss_index, ts_index = 0, 0, 0
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)
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)
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)
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)
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)
493 workbook.close()
494 return xlsx_file
496def uploadDataFile(file, fileId=None):
497 #
498 # upload DataFile to file-service
499 #
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}.')
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
523def importDataFile(xlsx_file, datafile_id=None):
524 #
525 # imports XLSX DataFile
526 #
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.')
534 # get data sheet name
535 sheet_name = 'data' if 'data' in xl.sheet_names() else xl.sheet_names()[0]
537 # upload data file
538 xlsx_file.seek(0)
539 r = uploadDataFile(file=xlsx_file, fileId=datafile_id)
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}.')
553 return datafile_id
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')
570 return file_id
572def importXLSX(xlsx_file, datafiles=None, item_id=None):
573 #
574 # imports testrun from xlsx file
575 #
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)
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.')
602 if testrun:
603 app.logger.info(f'Updating Testrun {testrun.uuid} from {xlsx_file.filename} by {current_user}.')
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()
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}"')
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)
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}')
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}')
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}.')
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}.')
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)
711 else:
712 # create default TestCaseSequence
714 # ClassName
715 # get ClassName from DB or create if it doesn't exist
716 classname = getOrCreateClassNameByName(CLASSNAME_TESTCASESEQUENCE, 'Default for TestCaseSequence.')
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)
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}')
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}.')
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]
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)
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
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
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}"')
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}.')
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
853 # ClassName
854 # get ClassName from DB or create if it doesn't exist
855 classname = getOrCreateClassNameByName(CLASSNAME_TESTCASE, 'Default for TestCase.')
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)
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
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
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
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)}')
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')
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)
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
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
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}')
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)
993 # get Locator
994 locator = None
995 if 'Locator' in headers:
996 locator = teststep_execution_sheet.cell(row, headers['Locator']).value or None
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)
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)
1012 # get Comparison
1013 comparison = None
1014 if 'Comparison' in headers:
1015 comparison = teststep_execution_sheet.cell(row, headers['Comparison']).value or None
1017 # get Timeout
1018 timeout = None
1019 if 'Timeout' in headers:
1020 timeout = teststep_execution_sheet.cell(row, headers['Timeout']).value or None
1022 # get Optional
1023 optional = False
1024 if 'Optional' in headers:
1025 optional = getBooleanValue(teststep_execution_sheet.cell(row, headers['Optional']).value)
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}')
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)
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)
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}.')
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)
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}')
1105 if r.status_code == 500:
1106 raise Exception('API Server interanal error')
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}')
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}')