//// TestRailInterface.cpp//// Created by Nissim Hadar on 6 Jul 2018.// Copyright 2013 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "TestRailInterface.h"#include "TestCreator.h" #include <quazip/quazip.h>#include <quazip/JlCompress.h> #include <QDateTime>#include <QFile>#include <QHostInfo>#include <QMessageBox>#include <QTextStream> TestRailInterface::TestRailInterface() : _pythonInterface(NULL) { _testRailTestCasesSelectorWindow.setURL("https://highfidelity.testrail.net"); _testRailTestCasesSelectorWindow.setUser("@highfidelity.io"); _testRailTestCasesSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); _testRailTestCasesSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); _testRailRunSelectorWindow.setURL("https://highfidelity.testrail.net"); _testRailRunSelectorWindow.setUser("@highfidelity.io"); _testRailRunSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); _testRailRunSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); _testRailResultsSelectorWindow.setURL("https://highfidelity.testrail.net"); _testRailResultsSelectorWindow.setUser("@highfidelity.io"); _testRailResultsSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); _testRailResultsSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); _pythonInterface = new PythonInterface(); _pythonCommand = _pythonInterface->getPythonCommand();} QString TestRailInterface::getObject(const QString& path) { return path.right(path.length() - path.lastIndexOf("/") - 1);} // Creates the testrail.py script// This is the file linked to from http://docs.gurock.com/testrail-api2/bindings-pythonvoid TestRailInterface::createTestRailDotPyScript() { QFile file(_outputDirectory + "/testrail.py"); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not create 'testrail.py'"); exit(-1); } QTextStream stream(&file); stream << "#\n"; stream << "# TestRail API binding for Python 3.x (API v2, available since \n"; stream << "# TestRail 3.0)\n"; stream << "#\n"; stream << "# Learn more:\n"; stream << "#\n"; stream << "# http://docs.gurock.com/testrail-api2/start\n"; stream << "# http://docs.gurock.com/testrail-api2/accessing\n"; stream << "#\n"; stream << "# Copyright Gurock Software GmbH. See license.md for details.\n"; stream << "#\n"; stream << "\n"; stream << "import urllib.request, urllib.error\n"; stream << "import json, base64\n"; stream << "\n"; stream << "class APIClient:\n"; stream << "\tdef __init__(self, base_url):\n"; stream << "\t\tself.user = ''\n"; stream << "\t\tself.password = ''\n"; stream << "\t\tif not base_url.endswith('/'):\n"; stream << "\t\t\tbase_url += '/'\n"; stream << "\t\tself.__url = base_url + 'index.php?/api/v2/'\n"; stream << "\n"; stream << "\t#\n"; stream << "\t# Send Get\n"; stream << "\t#\n"; stream << "\t# Issues a GET request (read) against the API and returns the result\n"; stream << "\t# (as Python dict).\n"; stream << "\t#\n"; stream << "\t# Arguments:\n"; stream << "\t#\n"; stream << "\t# uri The API method to call including parameters\n"; stream << "\t# (e.g. get_case/1)\n"; stream << "\t#\n"; stream << "\tdef send_get(self, uri):\n"; stream << "\t\treturn self.__send_request('GET', uri, None)\n"; stream << "\n"; stream << "\t#\n"; stream << "\t# Send POST\n"; stream << "\t#\n"; stream << "\t# Issues a POST request (write) against the API and returns the result\n"; stream << "\t# (as Python dict).\n"; stream << "\t#\n"; stream << "\t# Arguments:\n"; stream << "\t#\n"; stream << "\t# uri The API method to call including parameters\n"; stream << "\t# (e.g. add_case/1)\n"; stream << "\t# data The data to submit as part of the request (as\n"; stream << "\t# Python dict, strings must be UTF-8 encoded)\n"; stream << "\t#\n"; stream << "\tdef send_post(self, uri, data):\n"; stream << "\t\treturn self.__send_request('POST', uri, data)\n"; stream << "\n"; stream << "\tdef __send_request(self, method, uri, data):\n"; stream << "\t\turl = self.__url + uri\n"; stream << "\t\trequest = urllib.request.Request(url)\n"; stream << "\t\tif (method == 'POST'):\n"; stream << "\t\t\trequest.data = bytes(json.dumps(data), 'utf-8')\n"; stream << "\t\tauth = str(\n"; stream << "\t\t\tbase64.b64encode(\n"; stream << "\t\t\t\tbytes('%s:%s' % (self.user, self.password), 'utf-8')\n"; stream << "\t\t\t),\n"; stream << "\t\t\t'ascii'\n"; stream << "\t\t).strip()\n"; stream << "\t\trequest.add_header('Authorization', 'Basic %s' % auth)\n"; stream << "\t\trequest.add_header('Content-Type', 'application/json')\n"; stream << "\n"; stream << "\t\te = None\n"; stream << "\t\ttry:\n"; stream << "\t\t\tresponse = urllib.request.urlopen(request).read()\n"; stream << "\t\texcept urllib.error.HTTPError as ex:\n"; stream << "\t\t\tresponse = ex.read()\n"; stream << "\t\t\te = ex\n"; stream << "\n"; stream << "\t\tif response:\n"; stream << "\t\t\tresult = json.loads(response.decode())\n"; stream << "\t\telse:\n"; stream << "\t\t\tresult = {}\n"; stream << "\n"; stream << "\t\tif e != None:\n"; stream << "\t\t\tif result and 'error' in result:\n"; stream << "\t\t\t\terror = '\"' + result['error'] + '\"'\n"; stream << "\t\t\telse:\n"; stream << "\t\t\t\terror = 'No additional error message received'\n"; stream << "\t\t\traise APIError('TestRail API returned HTTP %s (%s)' % \n"; stream << "\t\t\t\t(e.code, error))\n"; stream << "\n"; stream << "\t\treturn result\n"; stream << "\n"; stream << "class APIError(Exception):\n"; stream << "\tpass\n"; file.close();} // Creates a Stack classvoid TestRailInterface::createStackDotPyScript() { QString filename = _outputDirectory + "/stack.py"; if (QFile::exists(filename)) { QFile::remove(filename); } QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not create 'stack.py'"); exit(-1); } QTextStream stream(&file); stream << "class Stack:\n"; stream << "\tdef __init__(self):\n"; stream << "\t\tself.items = []\n"; stream << "\n"; stream << "\tdef is_empty(self):\n"; stream << "\t\treturn self.items == []\n"; stream << "\n"; stream << "\tdef push(self, item):\n"; stream << "\t\tself.items.append(item)\n"; stream << "\n"; stream << "\tdef pop(self):\n"; stream << "\t\treturn self.items.pop()\n"; stream << "\n"; stream << "\tdef peek(self):\n"; stream << "\t\treturn self.items[len(self.items)-1]\n"; stream << "\n"; stream << "\tdef size(self):\n"; stream << "\t\treturn len(self.items)\n"; stream << "\n"; file.close();} bool TestRailInterface::requestTestRailTestCasesDataFromUser() { // Make sure correct fields are enabled before calling _testRailTestCasesSelectorWindow.reset(); _testRailTestCasesSelectorWindow.exec(); if (_testRailTestCasesSelectorWindow.getUserCancelled()) { return false; } _url = _testRailTestCasesSelectorWindow.getURL() + "/"; _user = _testRailTestCasesSelectorWindow.getUser(); _password = _testRailTestCasesSelectorWindow.getPassword(); _projectID = QString::number(_testRailTestCasesSelectorWindow.getProjectID()); _suiteID = QString::number(_testRailTestCasesSelectorWindow.getSuiteID()); return true;} bool TestRailInterface::requestTestRailRunDataFromUser() { _testRailRunSelectorWindow.reset(); _testRailRunSelectorWindow.exec(); if (_testRailRunSelectorWindow.getUserCancelled()) { return false; } _url = _testRailRunSelectorWindow.getURL() + "/"; _user = _testRailRunSelectorWindow.getUser(); _password = _testRailRunSelectorWindow.getPassword(); _projectID = QString::number(_testRailRunSelectorWindow.getProjectID()); _suiteID = QString::number(_testRailRunSelectorWindow.getSuiteID()); return true;} bool TestRailInterface::requestTestRailResultsDataFromUser() { _testRailResultsSelectorWindow.reset(); _testRailResultsSelectorWindow.exec(); if (_testRailResultsSelectorWindow.getUserCancelled()) { return false; } _url = _testRailResultsSelectorWindow.getURL() + "/"; _user = _testRailResultsSelectorWindow.getUser(); _password = _testRailResultsSelectorWindow.getPassword(); _projectID = QString::number(_testRailResultsSelectorWindow.getProjectID()); _suiteID = QString::number(_testRailResultsSelectorWindow.getSuiteID()); return true;} bool TestRailInterface::isAValidTestDirectory(const QString& directory) { if (TestCreator::isAValidDirectory(directory)) { // Ignore the utils and preformance directories if (directory.right(QString("utils").length()) == "utils" || directory.right(QString("performance").length()) == "performance") { return false; } return true; } return false;} void TestRailInterface::processDirectoryPython(const QString& directory, QTextStream& stream, const QString& userGitHub, const QString& branchGitHub) { // Loop over all entries in directory QDirIterator it(directory); while (it.hasNext()) { QString nextDirectory = it.next(); QString objectName = getObject(nextDirectory); if (isAValidTestDirectory(nextDirectory)) { // The name of the section is the directory at the end of the path stream << "parent_id = parent_ids.peek()\n"; stream << "data = { 'name': '" << objectName << "', 'suite_id': " + _suiteID + ", 'parent_id': parent_id }\n"; stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n"; // Now we push the parent_id, and recursively process each directory stream << "parent_ids.push(section['id'])\n\n"; processDirectoryPython(nextDirectory, stream, userGitHub, branchGitHub); } else if (objectName == "test.js") { processTestPython(nextDirectory, stream, userGitHub, branchGitHub); } } // pop the parent directory before leaving stream << "parent_ids.pop()\n\n";} // A suite of TestRail test cases contains trees.// The nodes of the trees are sections// The leaves are the test cases//// Each node and leaf have an ID and a parent ID.// Therefore, the tree is built top-down, using a stack to store the IDs of each node//void TestRailInterface::createAddTestCasesPythonScript(const QString& testDirectory, const QString& userGitHub, const QString& branchGitHub) { QString filename = _outputDirectory + "/addTestCases.py"; if (QFile::exists(filename)) { QFile::remove(filename); } QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not create 'addTestCases.py'"); exit(-1); } QTextStream stream(&file); // Code to access TestRail stream << "from testrail import *\n"; stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; stream << "client.user = '" << _user << "'\n"; stream << "client.password = '" << _password << "'\n\n"; stream << "from stack import *\n"; stream << "parent_ids = Stack()\n\n"; // top-level section stream << "data = { 'name': '" << "Test Section - " << QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm") + "', " << "'suite_id': " + _suiteID + "}\n"; stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n"; // Now we push the parent_id, and recursively process each directory stream << "parent_ids.push(section['id'])\n\n"; processDirectoryPython(testDirectory, stream, userGitHub, branchGitHub); file.close(); if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created", "Do you want to run the script and update TestRail?", QMessageBox::Yes | QMessageBox::No).exec() ) { QProcess* process = new QProcess(); _busyWindow.setWindowTitle("Updating TestRail"); connect(process, &QProcess::started, this, [=, this]() { _busyWindow.exec(); }); connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, [=, this](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); #ifdef Q_OS_WIN QStringList parameters = QStringList() << _outputDirectory + "/addTestCases.py"; process->start(_pythonCommand, parameters);#elif defined Q_OS_MAC QStringList parameters = QStringList() << "-c" << _pythonCommand + " " + _outputDirectory + "/addTestCases.py"; process->start("sh", parameters);#endif }} void TestRailInterface::updateReleasesComboData(int exitCode, QProcess::ExitStatus exitStatus) { // Quit if user has previously cancelled if (_testRailTestCasesSelectorWindow.getUserCancelled()) { return; } // Check if process completed successfully if (exitStatus != QProcess::NormalExit) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not get 'added to release' data from TestRail"); exit(-1); } // Create map of releases from the file created by the process _releaseNames.clear(); QString filename = _outputDirectory + "/releases.txt"; if (!QFile::exists(filename)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not find releases.txt in " + _outputDirectory); exit(-1); } QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not open " + _outputDirectory + "/releases.txt"); exit(-1); } QTextStream in(&file); QString line = in.readLine(); while (!line.isNull()) { _releaseNames << line; line = in.readLine(); } file.close(); // Update the combo _testRailTestCasesSelectorWindow.updateReleasesComboBoxData(_releaseNames); _testRailTestCasesSelectorWindow.exec(); if (_testRailTestCasesSelectorWindow.getUserCancelled()) { return; } createAddTestCasesPythonScript(_testDirectory, _userGitHub, _branchGitHub);} void TestRailInterface::addRun() { QString filename = _outputDirectory + "/addRun.py"; if (QFile::exists(filename)) { QFile::remove(filename); } QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not create " + filename); exit(-1); } QTextStream stream(&file); // Code to access TestRail stream << "from testrail import *\n"; stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; stream << "client.user = '" << _user << "'\n"; stream << "client.password = '" << _password << "'\n\n"; // A test suite is a forest. Each node is a section and leaves are either sections or test cases // The user has selected a root for the run // To find the cases in this tree we need all the sections in the tree // These are found by building a set of all relevant sections. The first section is the selected root // As the sections are in an ordered array we use the following snippet to find the relevant sections: // initialize section set with the root // for each section in the ordered array of sections in the suite // if the parent of the section is in the section set then // add this section to the section set // stream << "sections = client.send_get('get_sections/" + _projectID + "&suite_id=" + _suiteID + "')\n\n"; int sectionID = _sectionIDs[_testRailRunSelectorWindow.getSectionID()]; stream << "relevantSections = { " + QString::number(sectionID) + " }\n"; stream << "for section in sections:\n"; stream << "\tif section['parent_id'] in relevantSections:\n"; stream << "\t\trelevantSections.add(section['id'])\n\n"; // We now loop over each section in the set and collect the cases into an array stream << "cases = []\n"; stream << "for section_id in relevantSections:\n"; stream << "\tcases = cases + client.send_get('get_cases/" + _projectID + "&suite_id=" + _suiteID + "§ion_id=' + str(section_id))\n\n"; // To create a run we need an array of the relevant case ids stream << "case_ids = []\n"; stream << "for case in cases:\n"; stream << "\tcase_ids.append(case['id'])\n\n"; // Now, we can create the run stream << "data = { 'name': '" + _sectionNames[_testRailRunSelectorWindow.getSectionID()].replace("Section", "Run") + "[" + QHostInfo::localHostName() + "]" + "', 'suite_id': " + _suiteID + ", 'include_all': False, 'case_ids': case_ids}\n"; stream << "run = client.send_post('add_run/" + _projectID + "', data)\n"; file.close(); if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created", "Do you want to run the script and update TestRail?", QMessageBox::Yes | QMessageBox::No).exec() ) { QProcess* process = new QProcess(); _busyWindow.setWindowTitle("Updating TestRail"); connect(process, &QProcess::started, this, [=, this]() { _busyWindow.exec(); }); connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, [=, this](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); #ifdef Q_OS_WIN QStringList parameters = QStringList() << _outputDirectory + "/addRun.py"; process->start(_pythonCommand, parameters);#elif defined Q_OS_MAC QStringList parameters = QStringList() << "-c" << _pythonCommand + " " + _outputDirectory + "/addRun.py"; process->start("sh", parameters);#endif }}Lines 1–500 of 1199. Load more
// // TestRailInterface.cpp // // Created by Nissim Hadar on 6 Jul 2018. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "TestRailInterface.h" #include "TestCreator.h" #include <quazip/quazip.h> #include <quazip/JlCompress.h> #include <QDateTime> #include <QFile> #include <QHostInfo> #include <QMessageBox> #include <QTextStream> TestRailInterface::TestRailInterface() : _pythonInterface(NULL) { _testRailTestCasesSelectorWindow.setURL("https://highfidelity.testrail.net"); _testRailTestCasesSelectorWindow.setUser("@highfidelity.io"); _testRailTestCasesSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); _testRailTestCasesSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); _testRailRunSelectorWindow.setURL("https://highfidelity.testrail.net"); _testRailRunSelectorWindow.setUser("@highfidelity.io"); _testRailRunSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); _testRailRunSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); _testRailResultsSelectorWindow.setURL("https://highfidelity.testrail.net"); _testRailResultsSelectorWindow.setUser("@highfidelity.io"); _testRailResultsSelectorWindow.setProjectID(INTERFACE_AUTOMATION_PROJECT_ID); _testRailResultsSelectorWindow.setSuiteID(INTERFACE_SUITE_ID); _pythonInterface = new PythonInterface(); _pythonCommand = _pythonInterface->getPythonCommand(); } QString TestRailInterface::getObject(const QString& path) { return path.right(path.length() - path.lastIndexOf("/") - 1); } // Creates the testrail.py script // This is the file linked to from http://docs.gurock.com/testrail-api2/bindings-python void TestRailInterface::createTestRailDotPyScript() { QFile file(_outputDirectory + "/testrail.py"); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not create 'testrail.py'"); exit(-1); } QTextStream stream(&file); stream << "#\n"; stream << "# TestRail API binding for Python 3.x (API v2, available since \n"; stream << "# TestRail 3.0)\n"; stream << "#\n"; stream << "# Learn more:\n"; stream << "#\n"; stream << "# http://docs.gurock.com/testrail-api2/start\n"; stream << "# http://docs.gurock.com/testrail-api2/accessing\n"; stream << "#\n"; stream << "# Copyright Gurock Software GmbH. See license.md for details.\n"; stream << "#\n"; stream << "\n"; stream << "import urllib.request, urllib.error\n"; stream << "import json, base64\n"; stream << "\n"; stream << "class APIClient:\n"; stream << "\tdef __init__(self, base_url):\n"; stream << "\t\tself.user = ''\n"; stream << "\t\tself.password = ''\n"; stream << "\t\tif not base_url.endswith('/'):\n"; stream << "\t\t\tbase_url += '/'\n"; stream << "\t\tself.__url = base_url + 'index.php?/api/v2/'\n"; stream << "\n"; stream << "\t#\n"; stream << "\t# Send Get\n"; stream << "\t#\n"; stream << "\t# Issues a GET request (read) against the API and returns the result\n"; stream << "\t# (as Python dict).\n"; stream << "\t#\n"; stream << "\t# Arguments:\n"; stream << "\t#\n"; stream << "\t# uri The API method to call including parameters\n"; stream << "\t# (e.g. get_case/1)\n"; stream << "\t#\n"; stream << "\tdef send_get(self, uri):\n"; stream << "\t\treturn self.__send_request('GET', uri, None)\n"; stream << "\n"; stream << "\t#\n"; stream << "\t# Send POST\n"; stream << "\t#\n"; stream << "\t# Issues a POST request (write) against the API and returns the result\n"; stream << "\t# (as Python dict).\n"; stream << "\t#\n"; stream << "\t# Arguments:\n"; stream << "\t#\n"; stream << "\t# uri The API method to call including parameters\n"; stream << "\t# (e.g. add_case/1)\n"; stream << "\t# data The data to submit as part of the request (as\n"; stream << "\t# Python dict, strings must be UTF-8 encoded)\n"; stream << "\t#\n"; stream << "\tdef send_post(self, uri, data):\n"; stream << "\t\treturn self.__send_request('POST', uri, data)\n"; stream << "\n"; stream << "\tdef __send_request(self, method, uri, data):\n"; stream << "\t\turl = self.__url + uri\n"; stream << "\t\trequest = urllib.request.Request(url)\n"; stream << "\t\tif (method == 'POST'):\n"; stream << "\t\t\trequest.data = bytes(json.dumps(data), 'utf-8')\n"; stream << "\t\tauth = str(\n"; stream << "\t\t\tbase64.b64encode(\n"; stream << "\t\t\t\tbytes('%s:%s' % (self.user, self.password), 'utf-8')\n"; stream << "\t\t\t),\n"; stream << "\t\t\t'ascii'\n"; stream << "\t\t).strip()\n"; stream << "\t\trequest.add_header('Authorization', 'Basic %s' % auth)\n"; stream << "\t\trequest.add_header('Content-Type', 'application/json')\n"; stream << "\n"; stream << "\t\te = None\n"; stream << "\t\ttry:\n"; stream << "\t\t\tresponse = urllib.request.urlopen(request).read()\n"; stream << "\t\texcept urllib.error.HTTPError as ex:\n"; stream << "\t\t\tresponse = ex.read()\n"; stream << "\t\t\te = ex\n"; stream << "\n"; stream << "\t\tif response:\n"; stream << "\t\t\tresult = json.loads(response.decode())\n"; stream << "\t\telse:\n"; stream << "\t\t\tresult = {}\n"; stream << "\n"; stream << "\t\tif e != None:\n"; stream << "\t\t\tif result and 'error' in result:\n"; stream << "\t\t\t\terror = '\"' + result['error'] + '\"'\n"; stream << "\t\t\telse:\n"; stream << "\t\t\t\terror = 'No additional error message received'\n"; stream << "\t\t\traise APIError('TestRail API returned HTTP %s (%s)' % \n"; stream << "\t\t\t\t(e.code, error))\n"; stream << "\n"; stream << "\t\treturn result\n"; stream << "\n"; stream << "class APIError(Exception):\n"; stream << "\tpass\n"; file.close(); } // Creates a Stack class void TestRailInterface::createStackDotPyScript() { QString filename = _outputDirectory + "/stack.py"; if (QFile::exists(filename)) { QFile::remove(filename); } QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not create 'stack.py'"); exit(-1); } QTextStream stream(&file); stream << "class Stack:\n"; stream << "\tdef __init__(self):\n"; stream << "\t\tself.items = []\n"; stream << "\n"; stream << "\tdef is_empty(self):\n"; stream << "\t\treturn self.items == []\n"; stream << "\n"; stream << "\tdef push(self, item):\n"; stream << "\t\tself.items.append(item)\n"; stream << "\n"; stream << "\tdef pop(self):\n"; stream << "\t\treturn self.items.pop()\n"; stream << "\n"; stream << "\tdef peek(self):\n"; stream << "\t\treturn self.items[len(self.items)-1]\n"; stream << "\n"; stream << "\tdef size(self):\n"; stream << "\t\treturn len(self.items)\n"; stream << "\n"; file.close(); } bool TestRailInterface::requestTestRailTestCasesDataFromUser() { // Make sure correct fields are enabled before calling _testRailTestCasesSelectorWindow.reset(); _testRailTestCasesSelectorWindow.exec(); if (_testRailTestCasesSelectorWindow.getUserCancelled()) { return false; } _url = _testRailTestCasesSelectorWindow.getURL() + "/"; _user = _testRailTestCasesSelectorWindow.getUser(); _password = _testRailTestCasesSelectorWindow.getPassword(); _projectID = QString::number(_testRailTestCasesSelectorWindow.getProjectID()); _suiteID = QString::number(_testRailTestCasesSelectorWindow.getSuiteID()); return true; } bool TestRailInterface::requestTestRailRunDataFromUser() { _testRailRunSelectorWindow.reset(); _testRailRunSelectorWindow.exec(); if (_testRailRunSelectorWindow.getUserCancelled()) { return false; } _url = _testRailRunSelectorWindow.getURL() + "/"; _user = _testRailRunSelectorWindow.getUser(); _password = _testRailRunSelectorWindow.getPassword(); _projectID = QString::number(_testRailRunSelectorWindow.getProjectID()); _suiteID = QString::number(_testRailRunSelectorWindow.getSuiteID()); return true; } bool TestRailInterface::requestTestRailResultsDataFromUser() { _testRailResultsSelectorWindow.reset(); _testRailResultsSelectorWindow.exec(); if (_testRailResultsSelectorWindow.getUserCancelled()) { return false; } _url = _testRailResultsSelectorWindow.getURL() + "/"; _user = _testRailResultsSelectorWindow.getUser(); _password = _testRailResultsSelectorWindow.getPassword(); _projectID = QString::number(_testRailResultsSelectorWindow.getProjectID()); _suiteID = QString::number(_testRailResultsSelectorWindow.getSuiteID()); return true; } bool TestRailInterface::isAValidTestDirectory(const QString& directory) { if (TestCreator::isAValidDirectory(directory)) { // Ignore the utils and preformance directories if (directory.right(QString("utils").length()) == "utils" || directory.right(QString("performance").length()) == "performance") { return false; } return true; } return false; } void TestRailInterface::processDirectoryPython(const QString& directory, QTextStream& stream, const QString& userGitHub, const QString& branchGitHub) { // Loop over all entries in directory QDirIterator it(directory); while (it.hasNext()) { QString nextDirectory = it.next(); QString objectName = getObject(nextDirectory); if (isAValidTestDirectory(nextDirectory)) { // The name of the section is the directory at the end of the path stream << "parent_id = parent_ids.peek()\n"; stream << "data = { 'name': '" << objectName << "', 'suite_id': " + _suiteID + ", 'parent_id': parent_id }\n"; stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n"; // Now we push the parent_id, and recursively process each directory stream << "parent_ids.push(section['id'])\n\n"; processDirectoryPython(nextDirectory, stream, userGitHub, branchGitHub); } else if (objectName == "test.js") { processTestPython(nextDirectory, stream, userGitHub, branchGitHub); } } // pop the parent directory before leaving stream << "parent_ids.pop()\n\n"; } // A suite of TestRail test cases contains trees. // The nodes of the trees are sections // The leaves are the test cases // // Each node and leaf have an ID and a parent ID. // Therefore, the tree is built top-down, using a stack to store the IDs of each node // void TestRailInterface::createAddTestCasesPythonScript(const QString& testDirectory, const QString& userGitHub, const QString& branchGitHub) { QString filename = _outputDirectory + "/addTestCases.py"; if (QFile::exists(filename)) { QFile::remove(filename); } QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not create 'addTestCases.py'"); exit(-1); } QTextStream stream(&file); // Code to access TestRail stream << "from testrail import *\n"; stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; stream << "client.user = '" << _user << "'\n"; stream << "client.password = '" << _password << "'\n\n"; stream << "from stack import *\n"; stream << "parent_ids = Stack()\n\n"; // top-level section stream << "data = { 'name': '" << "Test Section - " << QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm") + "', " << "'suite_id': " + _suiteID + "}\n"; stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n"; // Now we push the parent_id, and recursively process each directory stream << "parent_ids.push(section['id'])\n\n"; processDirectoryPython(testDirectory, stream, userGitHub, branchGitHub); file.close(); if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created", "Do you want to run the script and update TestRail?", QMessageBox::Yes | QMessageBox::No).exec() ) { QProcess* process = new QProcess(); _busyWindow.setWindowTitle("Updating TestRail"); connect(process, &QProcess::started, this, [=, this]() { _busyWindow.exec(); }); connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, [=, this](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); #ifdef Q_OS_WIN QStringList parameters = QStringList() << _outputDirectory + "/addTestCases.py"; process->start(_pythonCommand, parameters); #elif defined Q_OS_MAC QStringList parameters = QStringList() << "-c" << _pythonCommand + " " + _outputDirectory + "/addTestCases.py"; process->start("sh", parameters); #endif } } void TestRailInterface::updateReleasesComboData(int exitCode, QProcess::ExitStatus exitStatus) { // Quit if user has previously cancelled if (_testRailTestCasesSelectorWindow.getUserCancelled()) { return; } // Check if process completed successfully if (exitStatus != QProcess::NormalExit) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not get 'added to release' data from TestRail"); exit(-1); } // Create map of releases from the file created by the process _releaseNames.clear(); QString filename = _outputDirectory + "/releases.txt"; if (!QFile::exists(filename)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not find releases.txt in " + _outputDirectory); exit(-1); } QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not open " + _outputDirectory + "/releases.txt"); exit(-1); } QTextStream in(&file); QString line = in.readLine(); while (!line.isNull()) { _releaseNames << line; line = in.readLine(); } file.close(); // Update the combo _testRailTestCasesSelectorWindow.updateReleasesComboBoxData(_releaseNames); _testRailTestCasesSelectorWindow.exec(); if (_testRailTestCasesSelectorWindow.getUserCancelled()) { return; } createAddTestCasesPythonScript(_testDirectory, _userGitHub, _branchGitHub); } void TestRailInterface::addRun() { QString filename = _outputDirectory + "/addRun.py"; if (QFile::exists(filename)) { QFile::remove(filename); } QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Could not create " + filename); exit(-1); } QTextStream stream(&file); // Code to access TestRail stream << "from testrail import *\n"; stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n"; stream << "client.user = '" << _user << "'\n"; stream << "client.password = '" << _password << "'\n\n"; // A test suite is a forest. Each node is a section and leaves are either sections or test cases // The user has selected a root for the run // To find the cases in this tree we need all the sections in the tree // These are found by building a set of all relevant sections. The first section is the selected root // As the sections are in an ordered array we use the following snippet to find the relevant sections: // initialize section set with the root // for each section in the ordered array of sections in the suite // if the parent of the section is in the section set then // add this section to the section set // stream << "sections = client.send_get('get_sections/" + _projectID + "&suite_id=" + _suiteID + "')\n\n"; int sectionID = _sectionIDs[_testRailRunSelectorWindow.getSectionID()]; stream << "relevantSections = { " + QString::number(sectionID) + " }\n"; stream << "for section in sections:\n"; stream << "\tif section['parent_id'] in relevantSections:\n"; stream << "\t\trelevantSections.add(section['id'])\n\n"; // We now loop over each section in the set and collect the cases into an array stream << "cases = []\n"; stream << "for section_id in relevantSections:\n"; stream << "\tcases = cases + client.send_get('get_cases/" + _projectID + "&suite_id=" + _suiteID + "§ion_id=' + str(section_id))\n\n"; // To create a run we need an array of the relevant case ids stream << "case_ids = []\n"; stream << "for case in cases:\n"; stream << "\tcase_ids.append(case['id'])\n\n"; // Now, we can create the run stream << "data = { 'name': '" + _sectionNames[_testRailRunSelectorWindow.getSectionID()].replace("Section", "Run") + "[" + QHostInfo::localHostName() + "]" + "', 'suite_id': " + _suiteID + ", 'include_all': False, 'case_ids': case_ids}\n"; stream << "run = client.send_post('add_run/" + _projectID + "', data)\n"; file.close(); if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created", "Do you want to run the script and update TestRail?", QMessageBox::Yes | QMessageBox::No).exec() ) { QProcess* process = new QProcess(); _busyWindow.setWindowTitle("Updating TestRail"); connect(process, &QProcess::started, this, [=, this]() { _busyWindow.exec(); }); connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, [=, this](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); #ifdef Q_OS_WIN QStringList parameters = QStringList() << _outputDirectory + "/addRun.py"; process->start(_pythonCommand, parameters); #elif defined Q_OS_MAC QStringList parameters = QStringList() << "-c" << _pythonCommand + " " + _outputDirectory + "/addRun.py"; process->start("sh", parameters); #endif } }