diff --git a/Development/src/components/PaginationButtons.js b/Development/src/components/PaginationButtons.js index 67ab71f..caaf3b2 100644 --- a/Development/src/components/PaginationButtons.js +++ b/Development/src/components/PaginationButtons.js @@ -36,7 +36,7 @@ export const PaginationButton = ({ }; return ( - <Button onClick={() => nextPage(rel)} disabled={!enabled}> + <Button onClick={() => nextPage(rel)} disabled={!enabled} name={rel}> {getIcon(rel)} {label} </Button> diff --git a/Development/src/pages/receivers/ConnectButtons.js b/Development/src/pages/receivers/ConnectButtons.js index 529ae41..a084ad2 100644 --- a/Development/src/pages/receivers/ConnectButtons.js +++ b/Development/src/pages/receivers/ConnectButtons.js @@ -65,6 +65,7 @@ const ConnectButtons = ({ senderData, receiverData }) => { onClick={event => handleConnect('active', event)} color="primary" startIcon={<ActivateImmediateIcon />} + name="activate" > Activate </Button> diff --git a/Development/src/pages/receivers/ConnectionManagementTab.js b/Development/src/pages/receivers/ConnectionManagementTab.js index c4c18b6..71e55ba 100644 --- a/Development/src/pages/receivers/ConnectionManagementTab.js +++ b/Development/src/pages/receivers/ConnectionManagementTab.js @@ -201,6 +201,7 @@ const ConnectionManagementTab = ({ receiverData, basePath }) => { style={{ textDecoration: 'none', }} + name="label" > <LinkChipField record={item} /> </Link> diff --git a/Development/src/pages/receivers/ReceiversList.js b/Development/src/pages/receivers/ReceiversList.js index d842eb2..e78175d 100644 --- a/Development/src/pages/receivers/ReceiversList.js +++ b/Development/src/pages/receivers/ReceiversList.js @@ -96,6 +96,7 @@ const ReceiversList = props => { basePath="/receivers" record={item} label={item.label} + name="label" /> </TableCell> <TableCell> @@ -117,6 +118,7 @@ const ReceiversList = props => { <ActiveField record={item} resource="receivers" + name="active" /> </TableCell> )} diff --git a/Development/src/pages/receivers/ReceiversShow.js b/Development/src/pages/receivers/ReceiversShow.js index d00d973..bce89b3 100644 --- a/Development/src/pages/receivers/ReceiversShow.js +++ b/Development/src/pages/receivers/ReceiversShow.js @@ -104,6 +104,7 @@ const ReceiversShowView = props => { disabled={ !get(record, `$${key}`) || !useConnectionAPI } + name={key} /> ))} <Tab @@ -114,6 +115,7 @@ const ReceiversShowView = props => { disabled={ !get(record, '$staged') || !useConnectionAPI } + name="connect" /> </Tabs> </Paper> @@ -140,10 +142,10 @@ const ShowSummaryTab = ({ record, ...props }) => { return ( <ShowView {...props} title={<ResourceTitle />} actions={<Fragment />}> <SimpleShowLayout> - <TextField label="ID" source="id" /> + <TextField label="ID" source="id" name="id" /> <TAIField source="version" /> - <TextField source="label" /> - <TextField source="description" /> + <TextField source="label" name="label" /> + <TextField source="description" name="description" /> <ObjectField source="tags" /> <SanitizedDivider /> <ParameterField source="transport" register={TRANSPORTS} /> @@ -181,7 +183,11 @@ const ShowSummaryTab = ({ record, ...props }) => { )} <ParameterField source="format" register={FORMATS} /> {queryVersion() >= 'v1.2' && ( - <BooleanField label="Active" source="subscription.active" /> + <BooleanField + label="Active" + source="subscription.active" + name="active" + /> )} {queryVersion() >= 'v1.2' && get(record, 'subscription.sender_id') && ( @@ -220,6 +226,7 @@ const ShowActiveTab = ({ record, ...props }) => { source="$active.sender_id" reference="senders" link="show" + name="sender" > <LinkChipField /> </ReferenceField> @@ -227,6 +234,7 @@ const ShowActiveTab = ({ record, ...props }) => { <BooleanField label="Master Enable" source="$active.master_enable" + name="master_enable" /> <TextField label="Mode" source="$active.activation.mode" /> <TAIField diff --git a/Development/src/pages/senders/SendersList.js b/Development/src/pages/senders/SendersList.js index 1156da4..19b3062 100644 --- a/Development/src/pages/senders/SendersList.js +++ b/Development/src/pages/senders/SendersList.js @@ -90,6 +90,7 @@ const SendersList = props => { basePath="/senders" record={item} label={item.label} + name="label" /> </TableCell> <TableCell> diff --git a/Development/src/pages/senders/SendersShow.js b/Development/src/pages/senders/SendersShow.js index dc0d5eb..d13f434 100644 --- a/Development/src/pages/senders/SendersShow.js +++ b/Development/src/pages/senders/SendersShow.js @@ -120,10 +120,10 @@ const ShowSummaryTab = ({ record, ...props }) => { return ( <ShowView {...props} title={<ResourceTitle />} actions={<Fragment />}> <SimpleShowLayout> - <TextField label="ID" source="id" /> + <TextField label="ID" source="id" name="id" /> <TAIField source="version" /> - <TextField source="label" /> - <TextField source="description" /> + <TextField source="label" name="label" /> + <TextField source="description" name="description" /> <ObjectField source="tags" /> <SanitizedDivider /> <ParameterField source="transport" register={TRANSPORTS} /> diff --git a/Development/src/pages/settings.js b/Development/src/pages/settings.js index b8b48cc..b497f35 100644 --- a/Development/src/pages/settings.js +++ b/Development/src/pages/settings.js @@ -95,6 +95,7 @@ const Settings = () => { onFocus={selectOnFocus} disabled={disabledSetting(QUERY_API)} helperText="Used to show the registered Nodes and their sub-resources" + name="queryapi" /> </StyledListItem> )} diff --git a/TestingFacade/.flake8 b/TestingFacade/.flake8 new file mode 100644 index 0000000..6deafc2 --- /dev/null +++ b/TestingFacade/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 diff --git a/TestingFacade/Config.py b/TestingFacade/Config.py new file mode 100644 index 0000000..b26f71e --- /dev/null +++ b/TestingFacade/Config.py @@ -0,0 +1,12 @@ +# Port for Testing Facade to run on +TESTING_FACADE_PORT = 5001 +# URL of nmos-js instance for testing +NCUT_URL = "http://localhost:3000/#/" +# URL of NMOS Testing Tool's mock registry +MOCK_REGISTRY_URL = "http://127.0.0.1:5102/" +# Browser to use for testing. Matching webdriver must be installed +BROWSER = 'Chrome' +# Use browser in headless mode. Set to False to have each test run in a visible window +HEADLESS = True +# Time in seconds to wait for elements to load +WAIT_TIME = 5 diff --git a/TestingFacade/DataStore.py b/TestingFacade/DataStore.py new file mode 100644 index 0000000..925c0e4 --- /dev/null +++ b/TestingFacade/DataStore.py @@ -0,0 +1,79 @@ +# Copyright (C) 2021 Advanced Media Workflow Association +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +class DataStore: + """ + Store json with test question details for use with NMOS Controller + test suite and Testing Facade + """ + + def __init__(self): + self.test_type = None + self.question_id = None + self.name = None + self.description = None + self.question = None + self.answers = None + self.timeout = None + self.answer_uri = None + self.answer_response = None + self.metadata = None + + def clear(self): + self.test_type = None + self.question_id = None + self.name = None + self.description = None + self.question = None + self.answers = None + self.timeout = None + self.answer_uri = None + self.answer_response = None + self.metadata = None + + def setJson(self, json): + self.test_type = json["test_type"] + self.question_id = json["question_id"] + self.name = json["name"] + self.description = json["description"] + self.question = json["question"] + self.answers = json["answers"] + self.timeout = json['timeout'] + self.answer_uri = json["answer_uri"] + self.metadata = json["metadata"] + + def getAnswerJson(self): + json_data = { + "question_id": self.question_id, + "answer_response": self.answer_response + } + return json_data + + def setAnswer(self, answer): + self.answer_response = answer + + def getQuestionID(self): + return self.question_id + + def getAnswers(self): + return self.answers + + def getUrl(self): + return self.answer_uri + + def getMetadata(self): + return self.metadata + + +dataStore = DataStore() diff --git a/TestingFacade/GenericAutoTest.py b/TestingFacade/GenericAutoTest.py new file mode 100644 index 0000000..883e322 --- /dev/null +++ b/TestingFacade/GenericAutoTest.py @@ -0,0 +1,128 @@ +import time +from selenium import webdriver +from selenium.webdriver.common.keys import Keys +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait +import Config as CONFIG + +class GenericAutoTest: + """ + Base test class for automated version of NMOS Controller test suite without Testing Façade + """ + def __init__(self): + self.NCuT_url = CONFIG.NCUT_URL + self.mock_registry_url = CONFIG.MOCK_REGISTRY_URL + self.multipart_question_storage = {} + + def set_up_test(self): + # Set up webdriver + browser = getattr(webdriver, CONFIG.BROWSER) + get_options = getattr(webdriver, CONFIG.BROWSER + 'Options', False) + if get_options: + options = get_options() + options.headless = CONFIG.HEADLESS + self.driver = browser(options=options) + else: + self.driver = browser() + self.driver.implicitly_wait(CONFIG.WAIT_TIME) + # Launch browser, navigate to nmos-js and update query api url to mock registry + self.driver.get(self.NCuT_url + "Settings") + query_api = self.driver.find_element_by_name("queryapi") + query_api.clear() + if query_api.get_attribute('value') != '': + time.sleep(1) + query_api.send_keys(Keys.CONTROL + "a") + query_api.send_keys(Keys.DELETE) + query_api.send_keys(self.mock_registry_url + "x-nmos/query/v1.3") + # Open menu to show link names if not already open + open_menu = self.driver.find_elements_by_xpath('//*[@title="Open menu"]') + if open_menu: + open_menu[0].click() + + def tear_down_test(self): + self.driver.close() + + def refresh_page(self): + """ + Click refresh button and sleep to allow loading time + """ + self.driver.find_element_by_css_selector("[aria-label='Refresh']").click() + time.sleep(1) + + def navigate_to_page(self, page): + """ + Navigate to page by link text, refresh page and sleep to allow loading time + """ + self.driver.find_element_by_link_text(page).click() + self.refresh_page() + + def find_resource_labels(self): + """ + Find all resources on a page by label + Returns list of labels + """ + resources = self.driver.find_elements_by_name("label") + return [entry.text for entry in resources] + + def next_page(self): + """ + Navigate to next page via next button and sleep to allow loading time + """ + self.driver.find_element_by_name('next').click() + time.sleep(1) + + def check_connectable(self): + """ + Check if connect tab is active + returns True if available, False if disabled + """ + connect_button = WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located((By.NAME, + "connect"))) + disabled = connect_button.get_attribute("aria-disabled") + return True if disabled == 'false' else False + + def make_connection(self, sender): + """ + Navigate to connect tab, activate connection to given sender + """ + connect = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.NAME, "connect"))) + connect.click() + + # Find the row containing the correct sender and activate connection + senders = self.find_resource_labels() + row = [i for i, s in enumerate(senders) if s == sender][0] + activate_button = self.driver.find_elements_by_name("activate")[row] + activate_button.click() + time.sleep(2) + + def remove_connection(self, receiver): + """ + Deactivate a connection on a given receiver + """ + receivers = self.find_resource_labels() + row = [i for i, r in enumerate(receivers) if r == receiver][0] + deactivate_button = self.driver.find_elements_by_name("active")[row] + if deactivate_button.get_attribute('value') == "true": + deactivate_button.click() + time.sleep(2) + + def get_active_receiver(self): + """ + Identify an active receiver + Returns string of receiver label or None + """ + active_buttons = self.driver.find_elements_by_name('active') + active_row = [i for i, b in enumerate(active_buttons) if b.get_attribute('value') == "true"] + return None if not active_row else self.driver.find_elements_by_name('label')[active_row[0]].text + + def get_connected_sender(self): + """ + Identify the sender a receiver is connected to + Returns string of sender label + """ + active = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.NAME, "active"))) + active.click() + + return self.driver.find_element_by_name('sender').text \ No newline at end of file diff --git a/TestingFacade/IS0404AutoTest.py b/TestingFacade/IS0404AutoTest.py new file mode 100644 index 0000000..bc0fa43 --- /dev/null +++ b/TestingFacade/IS0404AutoTest.py @@ -0,0 +1,153 @@ +import time +from GenericAutoTest import GenericAutoTest + + +class IS0404AutoTest(GenericAutoTest): + """ + Automated version of NMOS Controller test suite IS0404 + """ + + def test_01(self, answers, metadata): + """ + Ensure NCuT uses DNS-SD to find registry + """ + return "NMOS-js does not use DNS-SD to find registry" + + def test_02(self, answers, metadata): + """ + Ensure NCuT can access the IS-04 Query API + """ + # Use the NCuT to browse the Senders and Receivers on the + # discovered Registry via the selected IS-04 Query API. + # Once you have finished browsing click the 'Next' button. + # Successful browsing of the Registry will be automatically + # logged by the test framework. + + self.navigate_to_page('Senders') + self.navigate_to_page('Receivers') + + def test_03(self, answers, metadata): + """ + Query API should be able to discover all the senders that are + registered in the Registry + """ + # The NCuT should be able to discover all the Senders that are + # registered in the Registry. + # Refresh the NCuT's view of the Registry and carefully select + # the Senders that are available from the following list. + # For this test the registry paging limit has been set to 2. + # If your NCuT implements pagination, you must ensure you view + # every available page to complete this test. + + self.navigate_to_page('Senders') + + # Loop through pages gathering all senders + actual_answers = [] + + for i in range(len(answers)): + senders = self.find_resource_labels() + if not senders: + break + actual_answers += [answer['answer_id'] for answer in answers if answer['resource']['label'] + in senders] + self.next_page() + + return actual_answers + + def test_04(self, answers, metadata): + """ + Query API should be able to discover all the receivers that are + registered in the Registry + """ + # The NCuT should be able to discover all the Receivers that are + # registered in the Registry. + # Refresh the NCuT's view of the Registry and carefully select + # the Receivers that are available from the following list. + # For this test the registry paging limit has been set to 2. + # If your NCuT implements pagination, you must ensure you view + # every available page to complete this test. + + self.navigate_to_page('Receivers') + + # Loop through pages gathering all receivers + actual_answers = [] + + for i in range(len(answers)): + receivers = self.find_resource_labels() + if not receivers: + break + actual_answers += [answer['answer_id'] for answer in answers if answer['resource']['label'] + in receivers] + self.next_page() + + return actual_answers + + def test_05(self, answers, metadata): + """ + Reference Sender is put offline then back online + First question + """ + # The NCuT should be able to discover and dynamically update all + # the Senders that are registered in the Registry. + # Use the NCuT to browse and take note of the Senders that are + # available. + # After the 'Next' button has been clicked one of those senders + # will be put 'offline'. + + # Save current list of senders + self.navigate_to_page('Senders') + self.multipart_question_storage['test_05'] = self.find_resource_labels() + + def test_05_1(self, answers, metadata): + """ + Reference Sender is put offline then back online + Second question + """ + # Please refresh your NCuT and select the sender which has been put + # 'offline' + + # Get current list of senders and compare against previously saved list + self.navigate_to_page('Senders') + sender_list = self.find_resource_labels() + offline_sender = list(set(self.multipart_question_storage['test_05']) - set(sender_list)) + # Save offline sender + self.multipart_question_storage['test_05_1'] = offline_sender[0] + + actual_answer = [answer['answer_id'] for answer in answers + if answer['resource']['label'] == offline_sender[0]][0] + + return actual_answer + + def test_05_2(self, answers, metadata): + """ + Reference Sender is put offline then back online + Third question + """ + # The sender which was put 'offline' will come back online at a + # random moment within the next x seconds. + # As soon as the NCuT detects the sender has come back online + # please press the 'Next' button. + # The button must be pressed within x seconds of the Sender being + # put back 'online'. + # This includes any latency between the Sender being put 'online' + # and the NCuT updating. + + self.navigate_to_page('Senders') + sender_list = set() + + # Find all senders, keep checking until same as number of senders at start of test + while len(sender_list) < len(self.multipart_question_storage['test_05']): + time.sleep(4) + self.refresh_page() + senders = self.find_resource_labels() + sender_list.update(senders) + last_sender = senders[-1] + + # Check same sender came back + if last_sender == self.multipart_question_storage['test_05_1']: + return "Next" + else: + return "Unrecognised Sender" + + +IS0404tests = IS0404AutoTest() diff --git a/TestingFacade/IS0503AutoTest.py b/TestingFacade/IS0503AutoTest.py new file mode 100644 index 0000000..fba993f --- /dev/null +++ b/TestingFacade/IS0503AutoTest.py @@ -0,0 +1,132 @@ +import time +from GenericAutoTest import GenericAutoTest + + +class IS0503AutoTest(GenericAutoTest): + """ + Automated version of NMOS Controller test suite IS0503 + """ + def test_01(self, answers, metadata): + """ + Identify which Receiver devices are controllable via IS-05 + """ + # A subset of the Receivers registered with the Registry are controllable via IS-05, + # for instance, allowing Senders to be connected. + # Please refresh your NCuT and select the Receivers + # that have a Connection API from the list below. + # Be aware that if your NCuT only displays Receivers which have a Connection API, + # some of the Receivers in the following list may not be visible. + + self.navigate_to_page('Receivers') + receivers = self.find_resource_labels() + + # Loop through receivers and check if connection tab is disabled + connectable_receivers = [] + + for receiver in receivers: + self.navigate_to_page(receiver) + connectable = self.check_connectable() + if connectable: + connectable_receivers.append(receiver) + self.navigate_to_page('Receivers') + + # Get answer ids for connectable receivers to send to test suite + actual_answers = [answer['answer_id'] for answer in answers if answer['resource']['label'] + in connectable_receivers] + + return actual_answers + + def test_02(self, answers, metadata): + """ + Instruct Receiver to subscribe to a Sender’s Flow via IS-05 + """ + # All flows that are available in a Sender should be able to be + # connected to a Receiver. + # Use the NCuT to perform an 'immediate' activation between + # sender: x and receiver: y + # Click the 'Next' button once the connection is active. + + # Get sender and receiver details from metadata sent with question + sender = metadata['sender'] + receiver = metadata['receiver'] + + self.navigate_to_page('Receivers') + self.navigate_to_page(receiver['label']) + self.make_connection(sender['label']) + + def test_03(self, answers, metadata): + """ + Disconnecting a Receiver from a connected Flow via IS-05 + """ + # IS-05 provides a mechanism for removing an active connection + # through its API. + # Use the NCuT to remove the connection between sender: x and + # receiver: y + # Click the 'Next' button once the connection has been removed.' + + receiver = metadata['receiver'] + self.navigate_to_page('Receivers') + self.remove_connection(receiver['label']) + + def test_04(self, answers, metadata): + """ + Indicating the state of connections via updates received from the + IS-04 Query API + First question + """ + # The NCuT should be able to monitor and update the connection status + # of all registered Devices. + # Use the NCuT to identify the receiver that has just been activated. + + self.navigate_to_page('Receivers') + receiver = self.get_active_receiver() + + actual_answer = [answer['answer_id'] for answer in answers if answer['resource']['label'] == receiver][0] + + return actual_answer + + def test_04_1(self, answers, metadata): + """ + Indicating the state of connections via updates received from the + IS-04 Query API + Second question + """ + # Use the NCuT to identify the sender currently connected to receiver x + receiver = metadata['receiver'] + self.navigate_to_page('Receivers') + self.navigate_to_page(receiver['label']) + sender = self.get_connected_sender() + + actual_answer = [answer['answer_id'] for answer in answers if answer['resource']['label'] == sender][0] + + return actual_answer + + def test_04_2(self, answers, metadata): + """ + Indicating the state of connections via updates received from the + IS-04 Query API + Third Question + """ + # The connection on the following receiver will be disconnected + # at a random moment within the next x seconds. + # receiver x + # As soon as the NCuT detects the connection is inactive please + # press the 'Next' button. + # The button must be pressed within x seconds of the connection + # being removed. + # This includes any latency between the connection being removed + # and the NCuT updating. + + receiver = metadata['receiver'] + self.navigate_to_page('Receivers') + + # Periodically refresh until no receiver is active + for i in range(1, 20): + time.sleep(4) + self.refresh_page() + receiver = self.get_active_receiver() + if not receiver: + break + + +IS0503tests = IS0503AutoTest() diff --git a/TestingFacade/README.md b/TestingFacade/README.md new file mode 100644 index 0000000..4f6b3c7 --- /dev/null +++ b/TestingFacade/README.md @@ -0,0 +1,30 @@ +# NMOS Controller testing - Fully Automated testing of nmos-js + +## Installation and usage + +1. Install flask and selenium +`pip install -r requirements.txt` + +2. Install the webdriver for the browser you wish to use. [See selenium docs for more info.](https://www.selenium.dev/documentation/en/webdriver/driver_requirements/#quick-reference) + +3. Update the configuration file `Config.py` if necessary. + - `NCUT_URL` the url of your instance of nmos-js + - `MOCK_REGISTRY_URL` the url of the mock registry set up by the NMOS Controller test suite, included in the `pre_tests_message` + - `BROWSER` the name of the browser for which you installed the driver in step 2 + +4. Run the NMOS Testing tool (https://github.com/AMWA-TV/nmos-testing) and choose a Controller test suite + +5. Run TestingFacade.py with your chosen test suite specified using the `--suite` command line argument. Eg. `python3 TestingFacade.py --suite 'IS0404'`. +Currently supported test suites are: + - IS0404 + - IS0503 + +6. On your NMOS Testing instance enter the IP address and Port where the Automated Testing Facade is running + +7. Choose tests and click Run. The Testing Facade will launch a new headless browser at the beginning of each test and close it at the end. Note: Set the value of `HEADLESS` in `Config.py` to `False` to have the tests run in visible browser windows + +8. Test suite `POST`s the Question JSON to the TestingFacade API endpoint `/x-nmos/testquestion/{version}`. TestingFacade will retrieve the data and run the relevant set of selenium instructions defined in the test suite file to complete the test in your chosen browser then `POST`s the Answer JSON back to the test suite via the endpoint given in the `answer_uri` of the Question + +9. After the last test, test suite will `POST` a clear request to empty the data store + +10. Results are displayed on NMOS Testing tool diff --git a/TestingFacade/TestingFacade.py b/TestingFacade/TestingFacade.py new file mode 100644 index 0000000..40e07db --- /dev/null +++ b/TestingFacade/TestingFacade.py @@ -0,0 +1,111 @@ +# Copyright (C) 2021 Advanced Media Workflow Association +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import requests +import sys +from threading import Thread +from flask import Flask, request +from selenium.common.exceptions import NoSuchElementException +from DataStore import dataStore +from IS0404AutoTest import IS0404tests +from IS0503AutoTest import IS0503tests +import Config as CONFIG + +TEST_SETS = {'IS0404': IS0404tests, + 'IS0503': IS0503tests} + +app = Flask(__name__) + + +@app.route('/x-nmos/testquestion/<version>', methods=['POST'], strict_slashes=False) +def controller_tests_post(version): + # Should be json from Test Suite with questions + expected_entries = ['test_type', 'name', 'description', 'question', 'answers', 'answer_uri'] + + if request.json.get('clear'): + # End of current tests, clear data store + dataStore.clear() + else: + # Should be a new question + for entry in expected_entries: + if entry not in request.json: + return 'Invalid JSON received', 400 + # All required entries are present so update data + dataStore.setJson(request.json) + # Run test in new thread + executionThread = Thread(target=execute_test) + executionThread.start() + return '', 202 + + +def execute_test(): + """ + After test data has been sent to x-nmos/testing-facade figure out which + test was sent. Call relevant test method and retrieve answers. + Update json and send back to test suite + """ + # Get question details from data store + question_id = dataStore.getQuestionID() + answers = dataStore.getAnswers() + metadata = dataStore.getMetadata() + + # Load specified test suite + tests = TEST_SUITE + + if question_id.startswith("test_"): + # Get method associated with question id, set up test browser, + # run method then tear down and save any answers returned to data store + method = getattr(tests, question_id) + if callable(method): + print(" * Running " + question_id) + try: + tests.set_up_test() + test_result = method(answers, metadata) + except NoSuchElementException: + test_result = None + tests.tear_down_test() + dataStore.setAnswer(test_result) + + elif question_id == 'pre_tests_message': + # Beginning of test set, return to confirm start + dataStore.setAnswer(None) + + elif question_id == 'post_tests_message': + # End of test set, return to confirm end + dataStore.setAnswer(None) + print(' *** Tests Complete ***') + + else: + # Not a recognised part of test suite + dataStore.setAnswer(None) + + # POST answer json back to test suite + requests.post(dataStore.getUrl(), json=dataStore.getAnswerJson()) + return + + +if __name__ == "__main__": + global TEST_SUITE + + if '--suite' not in sys.argv: + sys.exit('You must specify a test suite with --suite') + + for i, arg in enumerate(sys.argv): + if arg == '--suite': + try: + TEST_SUITE = TEST_SETS[sys.argv[i+1]] + except Exception as e: + sys.exit('Invalid test suite selection ' + str(e)) + + app.run(host='0.0.0.0', port=CONFIG.TESTING_FACADE_PORT) diff --git a/TestingFacade/requirements.txt b/TestingFacade/requirements.txt new file mode 100644 index 0000000..14f4c60 --- /dev/null +++ b/TestingFacade/requirements.txt @@ -0,0 +1,3 @@ +flask>=1.0.0 +selenium +requests \ No newline at end of file