#! /usr/bin/env python3 # # BitBake Toaster functional tests implementation # # Copyright (C) 2017 Intel Corporation # # SPDX-License-Identifier: GPL-2.0-only # import os import logging import subprocess import signal import re import requests from django.urls import reverse from tests.browser.selenium_helpers_base import SeleniumTestCaseBase from selenium.webdriver.common.by import By from selenium.webdriver.support.select import Select from selenium.common.exceptions import NoSuchElementException logger = logging.getLogger("toaster") toaster_processes = [] class SeleniumFunctionalTestCase(SeleniumTestCaseBase): wait_toaster_time = 10 @classmethod def setUpClass(cls): # So that the buildinfo helper uses the test database' if os.environ.get('DJANGO_SETTINGS_MODULE', '') != \ 'toastermain.settings_test': raise RuntimeError("Please initialise django with the tests settings: " "DJANGO_SETTINGS_MODULE='toastermain.settings_test'") # Wait for any known toaster processes to exit global toaster_processes for toaster_process in toaster_processes: try: os.waitpid(toaster_process, os.WNOHANG) except ChildProcessError: pass # start toaster cmd = "bash -c 'source toaster start'" start_process = subprocess.Popen( cmd, cwd=os.environ.get("BUILDDIR"), shell=True) toaster_processes = [start_process.pid] if start_process.wait() != 0: port_use = os.popen("lsof -i -P -n | grep '8000 (LISTEN)'").read().strip() message = '' if port_use: process_id = port_use.split()[1] process = os.popen(f"ps -o cmd= -p {process_id}").read().strip() message = f"Port 8000 occupied by {process}" raise RuntimeError(f"Can't initialize toaster. {message}") builddir = os.environ.get("BUILDDIR") with open(os.path.join(builddir, '.toastermain.pid'), 'r') as f: toaster_processes.append(int(f.read())) with open(os.path.join(builddir, '.runbuilds.pid'), 'r') as f: toaster_processes.append(int(f.read())) super(SeleniumFunctionalTestCase, cls).setUpClass() cls.live_server_url = 'http://localhost:8000/' @classmethod def tearDownClass(cls): super(SeleniumFunctionalTestCase, cls).tearDownClass() global toaster_processes cmd = "bash -c 'source toaster stop'" stop_process = subprocess.Popen( cmd, cwd=os.environ.get("BUILDDIR"), shell=True) # Toaster stop has been known to hang in these tests so force kill if it stalls try: if stop_process.wait(cls.wait_toaster_time) != 0: raise Exception('Toaster stop process failed') except Exception as e: if e is subprocess.TimeoutExpired: print('Toaster stop process took too long. Force killing toaster...') else: print('Toaster stop process failed. Force killing toaster...') stop_process.kill() for toaster_process in toaster_processes: os.kill(toaster_process, signal.SIGTERM) def get_URL(self): rc=self.get_page_source() project_url=re.search(r"(projectPageUrl\s:\s\")(.*)(\",)",rc) return project_url.group(2) def find_element_by_link_text_in_table(self, table_id, link_text): """ Assume there're multiple suitable "find_element_by_link_text". In this circumstance we need to specify "table". """ try: table_element = self.get_table_element(table_id) element = table_element.find_element(By.LINK_TEXT, link_text) except NoSuchElementException: print('no element found') raise return element def get_table_element(self, table_id, *coordinate): if len(coordinate) == 0: #return whole-table element element_xpath = "//*[@id='" + table_id + "']" try: element = self.driver.find_element(By.XPATH, element_xpath) except NoSuchElementException: raise return element row = coordinate[0] if len(coordinate) == 1: #return whole-row element element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]" try: element = self.driver.find_element(By.XPATH, element_xpath) except NoSuchElementException: return False return element #now we are looking for an element with specified X and Y column = coordinate[1] element_xpath = "//*[@id='" + table_id + "']/tbody/tr[" + str(row) + "]/td[" + str(column) + "]" try: element = self.driver.find_element(By.XPATH, element_xpath) except NoSuchElementException: return False return element def create_new_project( self, project_name, release, release_title, merge_toaster_settings, ): """ Create/Test new project using: - Project Name: Any string - Release: Any string - Merge Toaster settings: True or False """ # Obtain a CSRF token from a suitable URL projs = requests.get(self.live_server_url + reverse('newproject')) csrftoken = projs.cookies.get('csrftoken') # Use the projects typeahead to find out if the project already exists req = requests.get(self.live_server_url + reverse('xhr_projectstypeahead'), {'search': project_name, 'format' : 'json'}) data = req.json() # Delete any existing projects for result in data['results']: del_url = reverse('xhr_project', args=(result['id'],)) del_response = requests.delete(self.live_server_url + del_url, cookies={'csrftoken': csrftoken}, headers={'X-CSRFToken': csrftoken}) self.assertEqual(del_response.status_code, 200) self.get(reverse('newproject')) self.wait_until_visible('#new-project-name') self.driver.find_element(By.ID, "new-project-name").send_keys(project_name) select = Select(self.find('#projectversion')) select.select_by_value(release) # check merge toaster settings checkbox = self.find('.checkbox-mergeattr') if merge_toaster_settings: if not checkbox.is_selected(): checkbox.click() else: if checkbox.is_selected(): checkbox.click() self.wait_until_clickable('#create-project-button') self.driver.find_element(By.ID, "create-project-button").click() element = self.wait_until_visible('#project-created-notification') self.assertTrue( self.element_exists('#project-created-notification'), f"Project:{project_name} creation notification not shown" ) self.assertTrue( project_name in element.text, f"New project name:{project_name} not in new project notification" ) # Use the projects typeahead again to check the project now exists req = requests.get(self.live_server_url + reverse('xhr_projectstypeahead'), {'search': project_name, 'format' : 'json'}) data = req.json() self.assertGreater(len(data['results']), 0, f"New project:{project_name} not found in database") project_id = data['results'][0]['id'] self.wait_until_visible('#project-release-title') # check release if release_title is not None: self.assertTrue(re.search( release_title, self.driver.find_element(By.XPATH, "//span[@id='project-release-title']" ).text), 'The project release is not defined') return project_id def load_projects_page_helper(self): self.wait_until_present('#projectstable') # Need to wait for some data in the table too self.wait_until_present('td[class="updated"]')