Browse Source

tests: help routes, delete & get items

aguryev 3 years ago
parent
commit
9c87249d8c

+ 1 - 1
ui/app/templates/base.html

@@ -66,7 +66,7 @@
                     </a>
                 </li>
                 <li class="nav-item">
-                    <a class="nav-link" href="{{ url_for('signup')}}">Sign up</a>
+                    <a class="nav-link" href="{{ url_for('register')}}">Sign up</a>
                 </li>
             {% endif %}
         </ul>

+ 0 - 1
ui/app/views.py

@@ -671,7 +671,6 @@ def dashboard():
 	return render_template('testrun/dashboard.html', testruns=testruns, stages=stages, stage_set=stage_set)
 
 
-
 #
 # user authentication
 #

ui/tests/test_db_dialects.py → ui/tests/_est_db_dialects.py


+ 0 - 526
ui/tests/_est_routes.py

@@ -1,526 +0,0 @@
-import pytest
-from tests.supports import login, get_csrf
-from app import models
-#from app.utils import COMPARISONS
-from lxml import html
-#from random import choice, sample, randrange
-#from math import ceil
-#import uuid
-
-# TODO:
-#
-# run_testrun       X
-# export_testrun    X
-# import_testsun    X
-# upload_datafile   X
-# update_datafile   X
-# update_testsun    X
-# get_results
-# get_charts
-# dashboard
-# help_index
-# help
-
-
-
-
-#
-# GET simple routes
-#
-def test_favicon(client):
-    r = client.get('/favicon.ico')
-    assert r.status_code == 200, f'status code: {r.status_code}'
-
-def test_home(client, user_data):
-    # unauthorized access
-    r = client.get('/')
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-    
-    # authorized access
-    login(client, user_data)
-    r = client.get('/')
-
-    # assert home page rendered
-    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
-    page = html.fromstring(r.data)    
-    title = page.xpath('./head/title')[0].text.strip()
-    assert 'home' in title.lower(), f"Title: {title}. Should contain 'Home'"
-
-    # assert links exist for every baangt element
-    items = [link.text.lower() for link in page.xpath('//div[@id="item-list"]/a')]
-    assert 'testruns' in items, "Item 'Testruns' is not in item list"
-    assert 'test case sequences' in items, "Item 'Test Case Sequences' is not in list"
-    assert 'test cases' in items, "Item 'Test Cases' is not in item list"
-    assert 'test step sequences' in items, "Item 'Test Step Sequences' is not in item list"
-    assert 'test steps' in items, "Item 'Test Steps' is not in item list"
-
-    # assert item links are valid
-    for link in page.xpath('//div[@id="item-list"]/a'):
-        r = client.get(link.attrib['href'])
-        assert r.status_code == 200, f'Accessing {link.attrib["href"]}. Status code: {r.status_code}'
-
-
-#
-# GET pages with baangt item lists 
-#
-@pytest.mark.parametrize('count', [(1,2), (5,1)])
-def test_testruns(client, user_data, populate):
-    url = '/testrun'
-
-    # unauthorized access
-    r = client.get(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-    
-    # authorized access
-    login(client, user_data)
-    r = client.get(url)
-
-    # assert testrun page rendered
-    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
-    page = html.fromstring(r.data)    
-    title = page.xpath('./head/title')[0].text.strip()
-    assert 'testruns' in title.lower(), f"Title: {title}. Should contain 'Testruns'"
-
-    # common buttons
-    assert bool(page.xpath('//a[@id="create-testrun"]')), "Button 'Create Testrun' does not exist"
-    assert bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import Testrun' does not exist"
-
-    # search field
-    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
-
-    # testrun details
-    # count
-    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
-    items = models.Testrun.query.all()
-    assert len(divs) == len(items)
-    # get random item
-    item = choice(items)
-    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
-    assert bool(div), 'Page does not contain the item'
-    div = div[0]
-    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "utton 'Edit' absents"
-    assert bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' absents"
-    assert bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' absents"
-    assert bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' absents"
-    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
-
-@pytest.mark.parametrize('count', [(1,1), (1,5)])
-def test_testcase_sequence(client, user_data, populate):
-    url = '/testcase_sequence'
-
-    # unauthorized access
-    r = client.get(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-    
-    # authorized access
-    login(client, user_data)
-    r = client.get(url)
-
-    # assert testcase_sequence page rendered
-    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
-    page = html.fromstring(r.data)    
-    title = page.xpath('./head/title')[0].text.strip()
-    assert 'test case sequences' in title.lower(), f"Title: {title}. Should contain 'Test Case Sequences'"
-
-    # common buttons
-    assert bool(page.xpath('//a[@id="create-testcase_sequence"]')), "Button 'Create Test Case Sequence' does not exist"
-    assert not bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import' should be hide"
-
-    # search field
-    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
-
-    # test case sequence details
-    # count
-    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
-    items = models.TestCaseSequence.query.all()
-    assert len(divs) == len(items)
-    # get random item
-    item = choice(items)
-    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
-    assert bool(div), 'Page does not contain the item'
-    div = div[0]
-    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "Button 'Edit' absents"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' should be hide"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' should be hide"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' should be hide"
-    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
-
-    # datafile buttons
-    assert bool(div.xpath('.//*[contains(@class, "btn-datafile-update")]')), "Datafile button 'Update' absents"
-    assert bool(div.xpath('.//*[contains(@class, "btn-datafile-download")]')), "Datafile button 'Download' absents"
-
-@pytest.mark.parametrize('count', [(1,1), (1,5)])
-def test_testcase(client, user_data, populate):
-    url = '/testcase'
-
-    # unauthorized access
-    r = client.get(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-    
-    # authorized access
-    login(client, user_data)
-    r = client.get(url)
-
-    # assert testcase_sequence page rendered
-    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
-    page = html.fromstring(r.data)    
-    title = page.xpath('./head/title')[0].text.strip()
-    assert 'test case' in title.lower(), f"Title: {title}. Should contain 'Test Cases'"
-
-    # common buttons
-    assert bool(page.xpath('//a[@id="create-testcase"]')), "Button 'Create Test Case' does not exist"
-    assert not bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import' should be hide"
-
-    # search field
-    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
-
-    # test case  details
-    # count
-    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
-    items = models.TestCase.query.all()
-    assert len(divs) == len(items)
-    # get random item
-    item = choice(items)
-    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
-    assert bool(div), 'Page does not contain the item'
-    div = div[0]
-    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "Button 'Edit' absents"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' should be hide"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' should be hide"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' should be hide"
-    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
-
-@pytest.mark.parametrize('count', [(1,1), (2,5)])
-def test_teststep_sequence(client, user_data, populate):
-    url = '/teststep_sequence'
-
-    # unauthorized access
-    r = client.get(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.get(url)
-
-    # assert testcase_sequence page rendered
-    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
-    page = html.fromstring(r.data)    
-    title = page.xpath('./head/title')[0].text.strip()
-    assert 'test step sequences' in title.lower(), f"Title: {title}. Should contain 'Test Step Sequences'"
-
-    # common buttons
-    assert bool(page.xpath('//a[@id="create-teststep_sequence"]')), "Button 'Create Test Step Sequence' does not exist"
-    assert not bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import' should be hide"
-
-    # search field
-    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
-
-    # test step sequence details
-    # count
-    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
-    items = models.TestStepSequence.query.all()
-    assert len(divs) == len(items)
-    # get random item
-    item = choice(items)
-    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
-    assert bool(div), 'Page does not contain the item'
-    div = div[0]
-    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "Button 'Edit' absents"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' should be hide"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' should be hide"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' should be hide"
-    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
-
-@pytest.mark.parametrize('count', [(1,1), (2,5)])
-def test_teststep(client, user_data, populate):
-    url = '/teststep'
-
-    # unauthorized access
-    r = client.get(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.get(url)
-
-    # assert testcase_sequence page rendered
-    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
-    page = html.fromstring(r.data)    
-    title = page.xpath('./head/title')[0].text.strip()
-    assert 'test steps' in title.lower(), f"Title: {title}. Should contain 'Test Steps'"
-
-    # common buttons
-    assert bool(page.xpath('//a[@id="create-teststep"]')), "Button 'Create Test Step' does not exist"
-    assert not bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import' should be hide"
-
-    # search field
-    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
-
-    # test step details
-    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
-    items = models.TestStepExecution.query.all()
-    assert len(divs) == len(items)
-    # get random item
-    item = choice(items)
-    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
-    assert bool(div), 'Page does not contain the item'
-    div = div[0]
-    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "Button 'Edit' absents"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' should be hide"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' should be hide"
-    assert not bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' should be hide"
-    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
-
-
-#
-# System POST Routes
-#
-@pytest.mark.parametrize('count', [(1,1), (5,3)])
-def test_delete_testrun_cascade(client, user_data, populate):
-    # delete testrun item and its children
-    item = models.Testrun.query.first()
-    url = f'/testrun/{item.uuid}/delete'
-
-    # unauthorized access
-    r = client.post(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.post(url)
-    assert r.status_code == 200, f'Deleting Testrun. Status code: {r.status_code}'
-    item_num = populate[0]-1
-    assert models.Testrun.query.count() == item_num, 'Testrun NOT deleted'
-    item_num *= populate[1]
-    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence NOT deleted'
-    assert models.DataFile.query.count() == item_num, 'Data File NOT deleted'
-    item_num *= populate[1]
-    assert models.TestCase.query.count() == item_num, 'Test Case NOT deleted'
-    item_num *= populate[1]
-    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence NOT deleted'
-    item_num *= populate[1]
-    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
-
-    # try to delete item by random id
-    url = f'/testrun/{uuid.uuid4()}/delete'
-    r = client.post(url)
-    assert r.status_code == 400, f'Deleting item that does not exist. Status code: {r.status_code}'
-
-@pytest.mark.parametrize('count', [(1,1), (5,3)])
-def test_delete_testcase_sequence_cascade(client, user_data, populate):
-    # delete testcase sequence item and its children
-    item = models.TestCaseSequence.query.first()
-    url = f'/testcase_sequence/{item.uuid}/delete'
-
-    # unauthorized access
-    r = client.post(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.post(url)
-    assert r.status_code == 200, f'Deleting TestCaseSequence. Status code: {r.status_code}'
-    item_num = populate[0]
-    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
-    item_num = populate[0]*populate[1] - 1
-    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence NOT deleted'
-    assert models.DataFile.query.count() == item_num, 'Data File NOT deleted'
-    item_num *= populate[1]
-    assert models.TestCase.query.count() == item_num, 'Test Case NOT deleted'
-    item_num *= populate[1]
-    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence NOT deleted'
-    item_num *= populate[1]
-    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
-
-@pytest.mark.parametrize('count', [(1,1), (5,3)])
-def test_delete_testcase_cascade(client, user_data, populate):
-    # delete testcase item and its children
-    item = models.TestCase.query.first()
-    url = f'/testcase/{item.uuid}/delete'
-    
-    # unauthorized access
-    r = client.post(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.post(url)
-    assert r.status_code == 200, f'Deleting TestCase. Status code: {r.status_code}'
-    item_num = populate[0]
-    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
-    item_num *= populate[1]
-    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
-    assert models.DataFile.query.count() == item_num, 'Data File deleted'
-    item_num = item_num*populate[1] - 1
-    assert models.TestCase.query.count() == item_num, 'Test Case NOT deleted'
-    item_num *= populate[1]
-    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence NOT deleted'
-    item_num *= populate[1]
-    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
-
-@pytest.mark.parametrize('count', [(1,1), (5,3)])
-def test_delete_teststep_sequence_cascade(client, user_data, populate):
-    # delete teststep sequence item and its children
-    item = models.TestStepSequence.query.first()
-    url = f'/teststep_sequence/{item.uuid}/delete'
-
-    # unauthorized access
-    r = client.post(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.post(url)
-    assert r.status_code == 200, f'Deleting TestStepSequence. Status code: {r.status_code}'
-    item_num = populate[0]
-    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
-    item_num *= populate[1]
-    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
-    assert models.DataFile.query.count() == item_num, 'Data File deleted'
-    item_num *= populate[1]
-    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
-    item_num = item_num*populate[1] - 1
-    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence NOT deleted'
-    item_num *= populate[1]
-    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
-
-@pytest.mark.parametrize('count', [(1,1), (5,3)])
-def test_delete_teststep_cascade(client, user_data, populate):
-    # delete teststep item
-    item = models.TestStepExecution.query.first()
-    url = f'/teststep/{item.uuid}/delete'
-
-    # unauthorized access
-    r = client.post(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.post(url)
-    assert r.status_code == 200, f'Deleting TestStep. Status code: {r.status_code}'
-    item_num = populate[0]
-    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
-    item_num *= populate[1]
-    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
-    assert models.DataFile.query.count() == item_num, 'Data File deleted'
-    item_num *= populate[1]
-    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
-    item_num *= populate[1]
-    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence deleted'
-    item_num = item_num*populate[1] - 1
-    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
-
-
-
-
-#
-# System DELETE Routes
-#
-@pytest.mark.parametrize('count', [(1,1), (3,2)])
-def test_delete_testrun_cascade(client, user_data, populate):
-    # delete testrun item
-    item = models.Testrun.query.first()
-    url = f'/testrun/{item.uuid}/delete'
-
-    # unauthorized access
-    r = client.delete(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.delete(url)
-    assert r.status_code == 200, f'Deleting Testrun. Status code: {r.status_code}'
-    item_num = populate[0]-1
-    assert models.Testrun.query.count() == item_num, 'Testrun NOT deleted'
-    item_num = populate[0]*populate[1]
-    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
-    assert models.DataFile.query.count() == item_num, 'Data File deleted'
-    item_num *= populate[1]
-    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
-    item_num *= populate[1]
-    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence deleted'
-    item_num *= populate[1]
-    assert models.TestStepExecution.query.count() == item_num, 'Test Step deleted'
-
-    # try to delete item by random id
-    url = f'/testrun/{uuid.uuid4()}/delete'
-    r = client.delete(url)
-    assert r.status_code == 400, f'Deleting item that does not exist. Status code: {r.status_code}'
-
-@pytest.mark.parametrize('count', [(1,1), (3,2)])
-def test_delete_testcase_sequence_cascade(client, user_data, populate):
-    # delete testcase sequence item
-    item = models.TestCaseSequence.query.first()
-    url = f'/testcase_sequence/{item.uuid}/delete'
-
-    # unauthorized access
-    r = client.delete(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.delete(url)
-    assert r.status_code == 200, f'Deleting TestCaseSequence. Status code: {r.status_code}'
-    item_num = populate[0]
-    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
-    item_num *= populate[1]
-    assert models.TestCaseSequence.query.count() == item_num-1, 'Test Case Sequence NOT deleted'
-    assert models.DataFile.query.count() == item_num, 'Data File deleted'
-    item_num *= populate[1]
-    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
-    item_num *= populate[1]
-    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence deleted'
-    item_num *= populate[1]
-    assert models.TestStepExecution.query.count() == item_num, 'Test Step deleted'
-
-@pytest.mark.parametrize('count', [(1,1), (5,3)])
-def test_delete_testcase_cascade(client, user_data, populate):
-    # delete testcase item
-    item = models.TestCase.query.first()
-    url = f'/testcase/{item.uuid}/delete'
-    
-    # unauthorized access
-    r = client.delete(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.delete(url)
-    assert r.status_code == 200, f'Deleting TestCase. Status code: {r.status_code}'
-    item_num = populate[0]
-    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
-    item_num *= populate[1]
-    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
-    assert models.DataFile.query.count() == item_num, 'Data File deleted'
-    item_num *= populate[1]
-    assert models.TestCase.query.count() == item_num-1, 'Test Case NOT deleted'
-    item_num *= populate[1]
-    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence deleted'
-    item_num *= populate[1]
-    assert models.TestStepExecution.query.count() == item_num, 'Test Step deleted'
-
-@pytest.mark.parametrize('count', [(1,1), (5,3)])
-def test_delete_teststep_sequence_cascade(client, user_data, populate):
-    # delete teststep sequence item and its children
-    item = models.TestStepSequence.query.first()
-    url = f'/teststep_sequence/{item.uuid}/delete'
-
-    # unauthorized access
-    r = client.delete(url)
-    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
-
-    # authorized access
-    login(client, user_data)
-    r = client.delete(url)
-    assert r.status_code == 200, f'Deleting TestStepSequence. Status code: {r.status_code}'
-    item_num = populate[0]
-    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
-    item_num *= populate[1]
-    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
-    assert models.DataFile.query.count() == item_num, 'Data File deleted'
-    item_num *= populate[1]
-    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
-    item_num *= populate[1]
-    assert models.TestStepSequence.query.count() == item_num-1, 'Test Step Sequence NOT deleted'
-    item_num *= populate[1]
-    assert models.TestStepExecution.query.count() == item_num, 'Test Step deleted'

+ 1 - 1
ui/tests/conftest.py

@@ -250,7 +250,7 @@ def populate(session, test_user, db_supports, count):
 
                         session.add(teststep)
 
-    # extra free steps
+    # extra free steps: 3
     if count[0] == 1:
         for i in range(3):
             teststep = models.TestStepExecution(

+ 11 - 0
ui/tests/data/doc_chapters.json

@@ -0,0 +1,11 @@
+[
+    "get_started",
+    "testrun",
+    "testcase_sequence",
+    "testcase",
+    "teststep_sequence",
+    "teststep",
+    "datafile",
+    "dashboard",
+    "report"
+]

+ 4 - 7
ui/tests/test_call_results.py

@@ -1,13 +1,13 @@
 import pytest
 from app import models
-from tests.supports import login, path_to_data, get_csrf, dump_html
+from tests.supports import login, path_to_data, get_csrf
 from lxml import html
 from random import choice
-import os
 import json
+import os
 import re
 
-'''
+
 @pytest.mark.parametrize('count', [(1,1)])
 def test_get_results_success(client, test_user, populate, set_tr_calls):
     # set-up  
@@ -206,7 +206,7 @@ def test_get_charts(client, test_user, populate, set_tr_calls):
             dataset['data']
         )
         assert all(d[0] == d[1] for d in call_data)
-'''
+
 
 @pytest.mark.parametrize('count', [(0,0), (1,2), (5,2)])
 def test_dashboard(client, test_user, populate, set_tr_calls):
@@ -235,7 +235,6 @@ def test_dashboard(client, test_user, populate, set_tr_calls):
     # authorized access
     login(client, test_user)
     r = client.get(url)
-    dump_html(r)
     assert r.status_code == 200, f'GET request. Status code: {r.status_code}'
     # assert dashbord page elements
     page = html.fromstring(r.data)
@@ -260,8 +259,6 @@ def test_dashboard(client, test_user, populate, set_tr_calls):
 
 
 
-
-
 ##### TODO:
 # POST: get_results
 

+ 286 - 0
ui/tests/test_delete_items.py

@@ -0,0 +1,286 @@
+import pytest
+from tests.supports import login
+from app import models
+from lxml import html
+from random import choice
+import uuid
+
+
+#
+# Cascade delete
+#
+@pytest.mark.parametrize('count', [(1,1), (5,3)])
+def test_delete_testrun_cascade(client, test_user, populate):
+    # delete testrun item and its children
+    item = choice(models.Testrun.query.all())
+    url = f'/testrun/{item.uuid}/delete'
+
+    # unauthorized access
+    r = client.post(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    # assert redirection url
+    page = html.fromstring(r.data)  
+    assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
+
+    # authorized access
+    login(client, test_user)
+    r = client.post(url)
+    assert r.status_code == 200, f'Deleting Testrun. Status code: {r.status_code}'
+    item_num = populate[0]-1
+    assert models.Testrun.query.count() == item_num, 'Testrun NOT deleted'
+    item_num *= populate[1]
+    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence NOT deleted'
+    assert models.DataFile.query.count() == item_num, 'Data File NOT deleted'
+    item_num *= populate[1]
+    assert models.TestCase.query.count() == item_num, 'Test Case NOT deleted'
+    item_num *= populate[1]
+    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence NOT deleted'
+    item_num = item_num*populate[1] + (3 if populate[0] == 1 else 0) # extra free steps for 1 testrun
+    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
+
+    # try to delete item by random id
+    url = f'/testrun/{uuid.uuid4()}/delete'
+    r = client.post(url)
+    assert r.status_code == 400, f'Deleting item that does not exist. Status code: {r.status_code}'
+
+@pytest.mark.parametrize('count', [(1,1), (5,3)])
+def test_delete_testcase_sequence_cascade(client, test_user, populate):
+    # delete testcase sequence item and its children
+    item = choice(models.TestCaseSequence.query.all())
+    url = f'/testcase_sequence/{item.uuid}/delete'
+
+    # unauthorized access
+    r = client.post(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    # assert redirection url
+    page = html.fromstring(r.data)  
+    assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
+
+    # authorized access
+    login(client, test_user)
+    r = client.post(url)
+    assert r.status_code == 200, f'Deleting TestCaseSequence. Status code: {r.status_code}'
+    item_num = populate[0]
+    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
+    item_num = populate[0]*populate[1] - 1
+    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence NOT deleted'
+    assert models.DataFile.query.count() == item_num, 'Data File NOT deleted'
+    item_num *= populate[1]
+    assert models.TestCase.query.count() == item_num, 'Test Case NOT deleted'
+    item_num *= populate[1]
+    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence NOT deleted'
+    item_num = item_num*populate[1] + (3 if populate[0] == 1 else 0) # extra free steps for 1 testrun
+    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
+
+@pytest.mark.parametrize('count', [(1,1), (5,3)])
+def test_delete_testcase_cascade(client, test_user, populate):
+    # delete testcase item and its children
+    item = choice(models.TestCase.query.all())
+    url = f'/testcase/{item.uuid}/delete'
+
+    # unauthorized access
+    r = client.post(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    # assert redirection url
+    page = html.fromstring(r.data)  
+    assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
+
+    # authorized access
+    login(client, test_user)
+    r = client.post(url)
+    assert r.status_code == 200, f'Deleting TestCase. Status code: {r.status_code}'
+    item_num = populate[0]
+    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
+    item_num *= populate[1]
+    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
+    assert models.DataFile.query.count() == item_num, 'Data File deleted'
+    item_num = item_num*populate[1] - 1
+    assert models.TestCase.query.count() == item_num, 'Test Case NOT deleted'
+    item_num *= populate[1]
+    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence NOT deleted'
+    item_num = item_num*populate[1] + (3 if populate[0] == 1 else 0) # extra free steps for 1 testrun
+    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
+
+@pytest.mark.parametrize('count', [(1,1), (5,3)])
+def test_delete_teststep_sequence_cascade(client, test_user, populate):
+    # delete teststep sequence item and its children
+    item = choice(models.TestStepSequence.query.all())
+    url = f'/teststep_sequence/{item.uuid}/delete'
+
+    # unauthorized access
+    r = client.post(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    # assert redirection url
+    page = html.fromstring(r.data)  
+    assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
+
+    # authorized access
+    login(client, test_user)
+    r = client.post(url)
+    assert r.status_code == 200, f'Deleting TestStepSequence. Status code: {r.status_code}'
+    item_num = populate[0]
+    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
+    item_num *= populate[1]
+    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
+    assert models.DataFile.query.count() == item_num, 'Data File deleted'
+    item_num *= populate[1]
+    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
+    item_num = item_num*populate[1] - 1
+    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence NOT deleted'
+    item_num = item_num*populate[1] + (3 if populate[0] == 1 else 0) # extra free steps for 1 testrun
+    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
+
+@pytest.mark.parametrize('count', [(1,1), (5,3)])
+def test_delete_teststep_cascade(client, test_user, populate):
+    # delete teststep item
+    item = choice(models.TestStepExecution.query.all())
+    url = f'/teststep/{item.uuid}/delete'
+
+    # unauthorized access
+    r = client.post(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    # assert redirection url
+    page = html.fromstring(r.data)  
+    assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
+
+    # authorized access
+    login(client, test_user)
+    r = client.post(url)
+    assert r.status_code == 200, f'Deleting TestStep. Status code: {r.status_code}'
+    item_num = populate[0]
+    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
+    item_num *= populate[1]
+    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
+    assert models.DataFile.query.count() == item_num, 'Data File deleted'
+    item_num *= populate[1]
+    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
+    item_num *= populate[1]
+    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence deleted'
+    item_num = item_num*populate[1] - 1 + (3 if populate[0] == 1 else 0) # extra for 1 testrun free steps
+    assert models.TestStepExecution.query.count() == item_num, 'Test Step NOT deleted'
+
+
+#
+# Delete only the item
+#
+@pytest.mark.parametrize('count', [(1,1), (3,2)])
+def test_delete_testrun(client, test_user, populate):
+    # delete testrun item
+    item = choice(models.Testrun.query.all())
+    url = f'/testrun/{item.uuid}/delete'
+
+    # unauthorized access
+    r = client.delete(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    # assert redirection url
+    page = html.fromstring(r.data)  
+    assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
+
+    # authorized access
+    login(client, test_user)
+    r = client.delete(url)
+    assert r.status_code == 200, f'Deleting Testrun. Status code: {r.status_code}'
+    item_num = populate[0]-1
+    assert models.Testrun.query.count() == item_num, 'Testrun NOT deleted'
+    item_num = populate[0]*populate[1]
+    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
+    assert models.DataFile.query.count() == item_num, 'Data File deleted'
+    item_num *= populate[1]
+    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
+    item_num *= populate[1]
+    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence deleted'
+    item_num = item_num*populate[1] + (3 if populate[0] == 1 else 0) # extra free steps for 1 testrun
+    assert models.TestStepExecution.query.count() == item_num, 'Test Step deleted'
+
+    # try to delete item by random id
+    url = f'/testrun/{uuid.uuid4()}/delete'
+    r = client.delete(url)
+    assert r.status_code == 400, f'Deleting item that does not exist. Status code: {r.status_code}'
+
+@pytest.mark.parametrize('count', [(1,1), (3,2)])
+def test_delete_testcase_sequence(client, test_user, populate):
+    # delete testcase sequence item
+    item = choice(models.TestCaseSequence.query.all())
+    url = f'/testcase_sequence/{item.uuid}/delete'
+
+    # unauthorized access
+    r = client.delete(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    # assert redirection url
+    page = html.fromstring(r.data)  
+    assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
+
+    # authorized access
+    login(client, test_user)
+    r = client.delete(url)
+    assert r.status_code == 200, f'Deleting TestCaseSequence. Status code: {r.status_code}'
+    item_num = populate[0]
+    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
+    item_num *= populate[1]
+    assert models.TestCaseSequence.query.count() == item_num-1, 'Test Case Sequence NOT deleted'
+    assert models.DataFile.query.count() == item_num, 'Data File deleted'
+    item_num *= populate[1]
+    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
+    item_num *= populate[1]
+    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence deleted'
+    item_num = item_num*populate[1] + (3 if populate[0] == 1 else 0) # extra free steps for 1 testrun
+    assert models.TestStepExecution.query.count() == item_num, 'Test Step deleted'
+
+@pytest.mark.parametrize('count', [(1,1), (5,3)])
+def test_delete_testcase(client, test_user, populate):
+    # delete testcase item
+    item = choice(models.TestCase.query.all())
+    url = f'/testcase/{item.uuid}/delete'
+    
+    # unauthorized access
+    r = client.delete(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    # assert redirection url
+    page = html.fromstring(r.data)  
+    assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
+
+    # authorized access
+    login(client, test_user)
+    r = client.delete(url)
+    assert r.status_code == 200, f'Deleting TestCase. Status code: {r.status_code}'
+    item_num = populate[0]
+    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
+    item_num *= populate[1]
+    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
+    assert models.DataFile.query.count() == item_num, 'Data File deleted'
+    item_num *= populate[1]
+    assert models.TestCase.query.count() == item_num-1, 'Test Case NOT deleted'
+    item_num *= populate[1]
+    assert models.TestStepSequence.query.count() == item_num, 'Test Step Sequence deleted'
+    item_num = item_num*populate[1] + (3 if populate[0] == 1 else 0) # extra free steps for 1 testrun
+    assert models.TestStepExecution.query.count() == item_num, 'Test Step deleted'
+
+@pytest.mark.parametrize('count', [(1,1), (5,3)])
+def test_delete_teststep_sequence(client, test_user, populate):
+    # delete teststep sequence item and its children
+    item = choice(models.TestStepSequence.query.all())
+    url = f'/teststep_sequence/{item.uuid}/delete'
+
+    # unauthorized access
+    r = client.delete(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    # assert redirection url
+    page = html.fromstring(r.data)  
+    assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
+
+    # authorized access
+    login(client, test_user)
+    r = client.delete(url)
+    assert r.status_code == 200, f'Deleting TestStepSequence. Status code: {r.status_code}'
+    item_num = populate[0]
+    assert models.Testrun.query.count() == item_num, 'Testrun deleted'
+    item_num *= populate[1]
+    assert models.TestCaseSequence.query.count() == item_num, 'Test Case Sequence deleted'
+    assert models.DataFile.query.count() == item_num, 'Data File deleted'
+    item_num *= populate[1]
+    assert models.TestCase.query.count() == item_num, 'Test Case deleted'
+    item_num *= populate[1]
+    assert models.TestStepSequence.query.count() == item_num-1, 'Test Step Sequence NOT deleted'
+    item_num = item_num*populate[1] + (3 if populate[0] == 1 else 0) # extra free steps for 1 testrun
+    assert models.TestStepExecution.query.count() == item_num, 'Test Step deleted'
+

+ 31 - 0
ui/tests/test_docs.py

@@ -0,0 +1,31 @@
+from tests.supports import login, path_to_data
+from lxml import html
+import json
+import os
+
+def test_docs_urls(client, test_user):
+    url = '/docs/'
+
+    # unauthorized access
+    r = client.get(url, follow_redirects=True)
+    assert r.status_code == 200
+
+    # unauthorized access
+    login(client, test_user)
+    r = client.get(url, follow_redirects=True)
+    assert r.status_code == 200
+
+    # assert links to doc chapters
+    with open(os.path.join(path_to_data, 'doc_chapters.json'), 'r') as f:
+        chapters = json.load(f)
+    page = html.fromstring(r.data)
+    chapter_div = page.xpath(f'//main/div')
+    assert bool(chapter_div)
+    chapter_links = chapter_div[0].xpath('./a')
+    assert len(chapter_links) == len(chapters)
+    for index, link in enumerate(chapter_links):
+        href = link.attrib.get('href')
+        assert href.split('/')[-1] in chapters, f'Link {index} is NOT in chapter list'
+        r = client.get(href)
+        assert r.status_code == 200, f'Accessing "{href}". Status code: {r.status_code}'
+

+ 1 - 1
ui/tests/test_export_testruns.py

@@ -49,7 +49,7 @@ def test_export_json(client, test_user, populate):
     r = client.get(url)
     assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
     # assert redirection url
-    page = html.fromstring(r.data)  
+    page = html.fromstring(r.data)
     assert '/login?' in page.xpath('//a')[0].attrib.get('href'), 'Unauthorized access: wrong redirection'
 
     # authorized access

+ 4 - 2
ui/tests/test_models.py

@@ -786,8 +786,10 @@ def test_testrun_call_update(session, test_user, populate):
     session.commit() 
 
     # update item
-    with open(path.join(path_to_data, 'testrun_call.json'), 'r') as f:
-        test_item = json.load(f)
+    with open(path.join(path_to_data, 'testrun_calls.json'), 'r') as f:
+        calls = json.load(f)
+    test_item = calls.get('success')[0]
+    test_item.update(calls.get('error')[0])
     testrun_call.is_failed = True
     testrun_call.error_message = test_item.get('error_msg')
     testrun_call.is_finished = True

+ 255 - 0
ui/tests/test_routes.py

@@ -0,0 +1,255 @@
+import pytest
+from tests.supports import login
+from app import models
+from lxml import html
+from random import choice
+
+
+
+#
+# GET simple routes
+#
+def test_favicon(client):
+    r = client.get('/favicon.ico')
+    assert r.status_code == 200, f'status code: {r.status_code}'
+
+def test_home(client, test_user):
+    # unauthorized access
+    r = client.get('/')
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    
+    # authorized access
+    login(client, test_user)
+    r = client.get('/')
+
+    # assert home page rendered
+    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
+    page = html.fromstring(r.data)    
+    title = page.xpath('./head/title')[0].text.strip()
+    assert 'home' in title.lower(), f"Title: {title}. Should contain 'Home'"
+
+    # assert links exist for every baangt element
+    items = [link.text.lower() for link in page.xpath('//div[@id="item-list"]/a')]
+    assert 'testruns' in items, "Item 'Testruns' is not in item list"
+    assert 'test case sequences' in items, "Item 'Test Case Sequences' is not in list"
+    assert 'test cases' in items, "Item 'Test Cases' is not in item list"
+    assert 'test step sequences' in items, "Item 'Test Step Sequences' is not in item list"
+    assert 'test steps' in items, "Item 'Test Steps' is not in item list"
+
+    # assert item links are valid
+    for link in page.xpath('//div[@id="item-list"]/a'):
+        r = client.get(link.attrib['href'])
+        assert r.status_code == 200, f'Accessing {link.attrib["href"]}. Status code: {r.status_code}'
+
+
+#
+# GET pages with baangt item lists 
+#
+@pytest.mark.parametrize('count', [(1,2), (5,1)])
+def test_testruns(client, test_user, populate):
+    url = '/testrun'
+
+    # unauthorized access
+    r = client.get(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    
+    # authorized access
+    login(client, test_user)
+    r = client.get(url)
+
+    # assert testrun page rendered
+    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
+    page = html.fromstring(r.data)    
+    title = page.xpath('./head/title')[0].text.strip()
+    assert 'testruns' in title.lower(), f"Title: {title}. Should contain 'Testruns'"
+
+    # common buttons
+    assert bool(page.xpath('//a[@id="create-testrun"]')), "Button 'Create Testrun' does not exist"
+    assert bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import Testrun' does not exist"
+
+    # search field
+    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
+
+    # testrun details
+    # count
+    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
+    items = models.Testrun.query.all()
+    assert len(divs) == len(items)
+    # get random item
+    item = choice(items)
+    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
+    assert bool(div), 'Page does not contain the item'
+    div = div[0]
+    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "utton 'Edit' absents"
+    assert bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' absents"
+    assert bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' absents"
+    assert bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' absents"
+    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
+
+@pytest.mark.parametrize('count', [(1,1), (1,5)])
+def test_testcase_sequence(client, test_user, populate):
+    url = '/testcase_sequence'
+
+    # unauthorized access
+    r = client.get(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    
+    # authorized access
+    login(client, test_user)
+    r = client.get(url)
+
+    # assert testcase_sequence page rendered
+    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
+    page = html.fromstring(r.data)    
+    title = page.xpath('./head/title')[0].text.strip()
+    assert 'test case sequences' in title.lower(), f"Title: {title}. Should contain 'Test Case Sequences'"
+
+    # common buttons
+    assert bool(page.xpath('//a[@id="create-testcase_sequence"]')), "Button 'Create Test Case Sequence' does not exist"
+    assert not bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import' should be hide"
+
+    # search field
+    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
+
+    # test case sequence details
+    # count
+    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
+    items = models.TestCaseSequence.query.all()
+    assert len(divs) == len(items)
+    # get random item
+    item = choice(items)
+    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
+    assert bool(div), 'Page does not contain the item'
+    div = div[0]
+    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "Button 'Edit' absents"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' should be hide"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' should be hide"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' should be hide"
+    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
+
+    # datafile buttons
+    assert bool(div.xpath('.//*[contains(@class, "btn-datafile-update")]')), "Datafile button 'Update' absents"
+    assert bool(div.xpath('.//*[contains(@class, "btn-datafile-download")]')), "Datafile button 'Download' absents"
+
+@pytest.mark.parametrize('count', [(1,1), (1,5)])
+def test_testcase(client, test_user, populate):
+    url = '/testcase'
+
+    # unauthorized access
+    r = client.get(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+    
+    # authorized access
+    login(client, test_user)
+    r = client.get(url)
+
+    # assert testcase_sequence page rendered
+    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
+    page = html.fromstring(r.data)    
+    title = page.xpath('./head/title')[0].text.strip()
+    assert 'test case' in title.lower(), f"Title: {title}. Should contain 'Test Cases'"
+
+    # common buttons
+    assert bool(page.xpath('//a[@id="create-testcase"]')), "Button 'Create Test Case' does not exist"
+    assert not bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import' should be hide"
+
+    # search field
+    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
+
+    # test case  details
+    # count
+    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
+    items = models.TestCase.query.all()
+    assert len(divs) == len(items)
+    # get random item
+    item = choice(items)
+    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
+    assert bool(div), 'Page does not contain the item'
+    div = div[0]
+    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "Button 'Edit' absents"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' should be hide"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' should be hide"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' should be hide"
+    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
+
+@pytest.mark.parametrize('count', [(1,1), (2,5)])
+def test_teststep_sequence(client, test_user, populate):
+    url = '/teststep_sequence'
+
+    # unauthorized access
+    r = client.get(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+
+    # authorized access
+    login(client, test_user)
+    r = client.get(url)
+
+    # assert testcase_sequence page rendered
+    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
+    page = html.fromstring(r.data)    
+    title = page.xpath('./head/title')[0].text.strip()
+    assert 'test step sequences' in title.lower(), f"Title: {title}. Should contain 'Test Step Sequences'"
+
+    # common buttons
+    assert bool(page.xpath('//a[@id="create-teststep_sequence"]')), "Button 'Create Test Step Sequence' does not exist"
+    assert not bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import' should be hide"
+
+    # search field
+    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
+
+    # test step sequence details
+    # count
+    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
+    items = models.TestStepSequence.query.all()
+    assert len(divs) == len(items)
+    # get random item
+    item = choice(items)
+    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
+    assert bool(div), 'Page does not contain the item'
+    div = div[0]
+    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "Button 'Edit' absents"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' should be hide"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' should be hide"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' should be hide"
+    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
+
+@pytest.mark.parametrize('count', [(1,1), (2,5)])
+def test_teststep(client, test_user, populate):
+    url = '/teststep'
+
+    # unauthorized access
+    r = client.get(url)
+    assert r.status_code == 302, f'Unauthorized access. Status code: {r.status_code}'
+
+    # authorized access
+    login(client, test_user)
+    r = client.get(url)
+
+    # assert testcase_sequence page rendered
+    assert r.status_code == 200, f'Authorized access. Status code: {r.status_code}'
+    page = html.fromstring(r.data)    
+    title = page.xpath('./head/title')[0].text.strip()
+    assert 'test steps' in title.lower(), f"Title: {title}. Should contain 'Test Steps'"
+
+    # common buttons
+    assert bool(page.xpath('//a[@id="create-teststep"]')), "Button 'Create Test Step' does not exist"
+    assert not bool(page.xpath('//button[@id="import-testrun"]')), "Button 'Import' should be hide"
+
+    # search field
+    assert bool(page.xpath('//input[@id="filter"]')), "Search field does not exist"
+
+    # test step details
+    divs = page.xpath(f'//div[contains(@class, "testrun-item")]')
+    items = models.TestStepExecution.query.all()
+    assert len(divs) == len(items)
+    # get random item
+    item = choice(items)
+    div = list(filter(lambda d: d.attrib.get('id') == f'item{item.uuid}', divs))
+    assert bool(div), 'Page does not contain the item'
+    div = div[0]
+    assert bool(div.xpath('.//*[contains(@class, "btn-edit")]')), "Button 'Edit' absents"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-update")]')), "Button 'Update' should be hide"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-export")]')), "Button 'Export' should be hide"
+    assert not bool(div.xpath('.//*[contains(@class, "btn-run")]')), "Button 'Run' should be hide"
+    assert bool(div.xpath('.//*[contains(@class, "btn-delete")]')), "Button 'Delete' absents"
+