├── engine ├── __init__.py ├── errors.py ├── utils │ ├── __init__.py │ ├── screenshot_utils.py │ └── utils.py ├── tests.py ├── admin.py ├── apps.py ├── urls.py ├── method_mapper.py ├── models.py ├── test_result_utils.py ├── email_generator.py ├── alert_sender.py ├── logs.py ├── thread_executor.py ├── testreport_generator.py ├── executor.py ├── api_response.py └── views.py ├── .gitattributes ├── version.txt ├── .DS_Store ├── recorder └── extensions │ └── chrome │ ├── src │ ├── App.css │ ├── Icons │ │ ├── BackButton.png │ │ ├── PlayButton.png │ │ ├── RightArrow.png │ │ ├── StopButton.png │ │ └── PauseButton.png │ ├── setupTests.js │ ├── reportWebVitals.js │ ├── ComponentCss │ │ ├── dakshaRecorderStartingPage.css │ │ ├── dakshaRecorderEndPage.css │ │ ├── dakshaRecorderCustomHardwaitPage.css │ │ └── dakshaRecorderMainPage.css │ ├── index.css │ ├── index.js │ ├── Components │ │ ├── globalConfigs.js │ │ ├── setBadgeForRecording.js │ │ ├── removeBadgeForRecording.js │ │ ├── dakshaRecorderStartingPage.js │ │ ├── dakshaRecorderCustomHardwaitPage.js │ │ ├── dakshaRecorderEndPage.js │ │ └── dakshaRecorderMainPage.js │ ├── App.js │ └── ContentScript.js │ ├── public │ ├── robots.txt │ ├── 128.png │ ├── favicon.ico │ ├── RecordingIcon.svg │ ├── index.html │ ├── RecordingTab.css │ ├── manifest.json │ ├── RecordingTab.html │ ├── RecordingTab.js │ └── EventPage.js │ ├── webpack.config.js │ ├── .gitignore │ ├── package.json │ └── ReadMe.md ├── CODEOWNERS ├── daksha_know-how ├── HelloWorld.yml ├── ApiRequest.md ├── CronJobsDescription.md ├── QuickStart.md ├── CreateTest.md └── RequestAPI.md ├── RELEASE.md ├── startup_command.bat ├── templates ├── report.html ├── slack_alert.json ├── gchat_alert.json ├── report.css └── report.js ├── examples ├── variablesupportTestexample.yml ├── Alert_example.yml ├── wait_example.yml └── API_support.yml ├── requirements.txt ├── daksha ├── __init__.py ├── cron.py ├── asgi.py ├── wsgi.py ├── urls.py ├── rp_client_ext.py └── settings.py ├── startup_command.sh ├── manage.py ├── Dockerfile ├── startup_command_mac.sh ├── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTING.md ├── docker-compose.yml ├── .gitignore ├── CHANGELOG.md └── README.md /engine/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | daksha=2.11.1 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mykaarma/daksha/HEAD/.DS_Store -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Rishi16royy @rajatsharda04 @prakash-42 @hriddhighosh4 @AlphaRahul16 2 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mykaarma/daksha/HEAD/recorder/extensions/chrome/public/128.png -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mykaarma/daksha/HEAD/recorder/extensions/chrome/public/favicon.ico -------------------------------------------------------------------------------- /engine/errors.py: -------------------------------------------------------------------------------- 1 | class BadArgumentsError(Exception): 2 | pass 3 | 4 | 5 | class UnsupportedFileSourceError(BadArgumentsError): 6 | pass 7 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Icons/BackButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mykaarma/daksha/HEAD/recorder/extensions/chrome/src/Icons/BackButton.png -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Icons/PlayButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mykaarma/daksha/HEAD/recorder/extensions/chrome/src/Icons/PlayButton.png -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Icons/RightArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mykaarma/daksha/HEAD/recorder/extensions/chrome/src/Icons/RightArrow.png -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Icons/StopButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mykaarma/daksha/HEAD/recorder/extensions/chrome/src/Icons/StopButton.png -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Icons/PauseButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mykaarma/daksha/HEAD/recorder/extensions/chrome/src/Icons/PauseButton.png -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/RecordingIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './src/ContentScript.js', 6 | output: { 7 | path: path.resolve(__dirname, 'public'), 8 | filename: 'ContentScript.js', 9 | }, 10 | }; -------------------------------------------------------------------------------- /daksha_know-how/HelloWorld.yml: -------------------------------------------------------------------------------- 1 | config: 2 | env: remote 3 | browser: chrome 4 | driverAddress: http://selenium-hub:4444/wd/hub 5 | name: HelloWorld 6 | task: 7 | - open_url: 8 | url: https://www.google.com/ 9 | - wait_for: 10 | mode: hardwait 11 | value: 100 12 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | To create a release do the following steps: 2 | 1. Go to releases in the [repo](https://github.com/mykaarma/daksha/releases). 3 | 2. Click on `Draft a new realease`. 4 | 3. Click on `Choose a tag`, type the tag. The tag should be the version mentioned in your [CHANGELOG.md](/CHANGELOG.md). 5 | 4. Provide a release title and description, and publish the release. 6 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /public/ContentScript.js 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /startup_command.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set "TEST_RESULT_DB=%TEST_RESULT_DB%" 4 | 5 | @REM If the environment variable TEST_RESULT_DB is not set, we will directly run the server 6 | if not defined TEST_RESULT_DB goto :runserver 7 | 8 | @REM The parameter /i tells the cmd to do a case-insensitive comparison 9 | IF /i "%TEST_RESULT_DB%"=="postgres" ( 10 | python manage.py makemigrations engine 11 | python manage.py migrate 12 | ) 13 | 14 | :runserver 15 | python manage.py runserver 16 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/ComponentCss/dakshaRecorderStartingPage.css: -------------------------------------------------------------------------------- 1 | .mainDiv{ 2 | display: flex; 3 | justify-content: space-evenly; 4 | flex-direction: column; 5 | height: 80px; 6 | width: 200px; 7 | font-size: medium; 8 | font-family: 'Lato', sans-serif; 9 | } 10 | 11 | .recording-button-div{ 12 | display: flex; 13 | background:#0377B3; 14 | color: white; 15 | height: 40%; 16 | border-radius: 5px; 17 | cursor: pointer; 18 | align-items: center; 19 | justify-content: center; 20 | } -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | #root{ 15 | background-color: black; 16 | height: 180px; 17 | width: 180px; 18 | } -------------------------------------------------------------------------------- /templates/report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

Test Report for test : ${test_uuid}

13 |
14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /templates/slack_alert.json: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": [ 3 | { 4 | "type": "section", 5 | "text": { 6 | "type": "mrkdwn", 7 | "text": "${body}" 8 | } 9 | }, 10 | { 11 | "type": "section", 12 | "text": { 13 | "type": "mrkdwn", 14 | "text": "*You can refer to the Daksha Wiki here*" 15 | }, 16 | "accessory": { 17 | "type": "button", 18 | "text": { 19 | "type": "plain_text", 20 | "text": "Daksha Wiki", 21 | "emoji": true 22 | }, 23 | "value": "click_me_123", 24 | "url": "https://github.com/mykaarma/daksha/wiki", 25 | "action_id": "button-action" 26 | } 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /examples/variablesupportTestexample.yml: -------------------------------------------------------------------------------- 1 | config: 2 | env: local 3 | browser: chrome 4 | driverAddress: D:\Recover\chromedriver_win32\chromedriver.exe 5 | # driverAddress: http://192.168.21.185:4444/wd/hub 6 | name: VariableSupportTestExample 7 | task: 8 | - launch_browser 9 | - open_url: 10 | url: http://the-internet.herokuapp.com/ 11 | - click_button: 12 | xpath: //a[normalize-space()='Inputs'] 13 | - validate_ui_element: 14 | mode: equals 15 | xpath: //h3[normalize-space()='Inputs'] 16 | value: '{{ check }}' 17 | - fill_data: 18 | xpath: //input[@type='number'] 19 | value: '{{ number }}' 20 | 21 | - quit_browser 22 | -------------------------------------------------------------------------------- /templates/gchat_alert.json: -------------------------------------------------------------------------------- 1 | { 2 | "text": "${body}", 3 | "cards": [ 4 | { 5 | "sections": [ 6 | { 7 | "widgets": [ 8 | { 9 | "buttons": [ 10 | 11 | { 12 | "textButton": { 13 | "text": "daksha Wiki", 14 | "onClick": { 15 | "openLink": { 16 | "url": "https://github.com/mykaarma/daksha/wiki" 17 | } 18 | } 19 | } 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | ] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | React App 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.4.1 2 | Django==4.0.2 3 | djangorestframework==3.13.1 4 | gunicorn==20.0.4 5 | pytz==2020.1 6 | PyYAML==6.0 7 | selenium==3.141.0 8 | sqlparse==0.3.1 9 | urllib3==1.26.7 10 | PyGithub==1.53 11 | pyyaml==6.0 12 | postmarker==0.14.1 13 | Jinja2==2.11.3 14 | jmespath==0.10.0 15 | # Temporary patch for markupsafe issue https://github.com/pallets/markupsafe/issues/284 16 | markupsafe>=1.1.1,<2.1.0 17 | #psycopg2 is needed for establishing connection with Postgresql database 18 | psycopg2==2.9.6 19 | #For deploying the application on Mac, comment out the above line and uncomment the below line 20 | #psycopg2-binary==2.9.6 21 | django_crontab==0.7.1 22 | reportportal-client==5.6.5 23 | -------------------------------------------------------------------------------- /examples/Alert_example.yml: -------------------------------------------------------------------------------- 1 | config: 2 | env: local 3 | browser: chrome 4 | driverAddress: /Users/hriddhi/Downloads/chromedriver 2 5 | name: Wait_test 6 | alert_type: slack 7 | task: 8 | - launch_browser 9 | - open_url: 10 | url: https://www.google.co.in/ 11 | - wait_for: 12 | mode: visibility 13 | xpath: //input[@title ="Search"] 14 | - fill_data: 15 | xpath: //input[@title ="Searchn"] 16 | value: Cars 17 | - wait_for: 18 | mode: hardwait 19 | value: 10 20 | - click_button: 21 | xpath: //div[@role="option"]/div/span[text() ="CARS24"] 22 | - wait_for: 23 | mode: invisibility 24 | xpath: //input[@title ="Search"] 25 | - quit_browser 26 | -------------------------------------------------------------------------------- /daksha/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | -------------------------------------------------------------------------------- /engine/utils/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ -------------------------------------------------------------------------------- /examples/wait_example.yml: -------------------------------------------------------------------------------- 1 | config: 2 | env: local 3 | browser: chrome 4 | driverAddress: /Users/user/Downloads/chromedriver 5 | name: Wait_test 6 | task: 7 | - launch_browser 8 | - open_url: 9 | url: https://www.google.co.in/ 10 | - wait_for: 11 | mode: visibility 12 | xpath: //input[@title ="Search"] 13 | - fill_data: 14 | xpath: //input[@title ="Search"] 15 | value: Cars 16 | - wait_for: 17 | mode: hardwait 18 | value: 10 19 | - click_button: 20 | xpath: //div[@role="option"]/div/span[text() ="CARS24"] 21 | - wait_for: 22 | mode: invisibility 23 | xpath: //input[@title ="Search"] 24 | - scroll_to: 25 | xpath: //span[contains(.,"Next")] 26 | - quit_browser 27 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/ComponentCss/dakshaRecorderEndPage.css: -------------------------------------------------------------------------------- 1 | .end-page-container{ 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-evenly; 5 | height: 175px; 6 | width: 215px; 7 | font-size: medium; 8 | user-select: none; 9 | font-family: 'Lato', sans-serif; 10 | } 11 | .end-page-all-options-container{ 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: space-evenly; 15 | height: 125px; 16 | } 17 | .end-page-each-option-div{ 18 | border-radius: 7px; 19 | border-color: #0377B3; 20 | border-style: solid; 21 | text-align: center; 22 | padding: 5px; 23 | cursor: pointer; 24 | } 25 | 26 | #end-page-recording-button{ 27 | background-color: #0377B3; 28 | color: white; 29 | } 30 | -------------------------------------------------------------------------------- /engine/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | from django.test import TestCase 19 | 20 | # Create your tests here. 21 | -------------------------------------------------------------------------------- /engine/admin.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | from django.contrib import admin 19 | 20 | # Register your models here. 21 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/RecordingTab.css: -------------------------------------------------------------------------------- 1 | .main-container { 2 | top:0px; 3 | left: 0px; 4 | position: fixed; 5 | display: flex; 6 | height: 100%; 7 | width: 100%; 8 | font-family: 'Lato', sans-serif; 9 | } 10 | .left-div { 11 | position: relative; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | width: 20%; 16 | height: 100%; 17 | user-select: none; 18 | background-color: white; 19 | } 20 | 21 | .right-div { 22 | position: relative; 23 | width: 80%; 24 | height: 100%; 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | user-select: none; 29 | background-color: white; 30 | color: black; 31 | font-weight: 100; 32 | } 33 | 34 | .right-div-text{ 35 | font-size: 19px; 36 | margin-top: 16px; 37 | font-weight: 200; 38 | } -------------------------------------------------------------------------------- /engine/apps.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | 19 | from django.apps import AppConfig 20 | 21 | 22 | class EngineConfig(AppConfig): 23 | name = 'engine' 24 | -------------------------------------------------------------------------------- /daksha/cron.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | import requests 19 | from daksha import settings 20 | 21 | def cron_job_executor(parameters): 22 | requests.post(settings.daksha_endpoint,json=parameters) 23 | 24 | -------------------------------------------------------------------------------- /engine/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | from django.urls import path 19 | 20 | from . import views 21 | 22 | urlpatterns = [ 23 | path('runner', views.executor, name='singletest'), 24 | path('tests/',views.testresultsretriever) 25 | ] -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import App from './App'; 20 | 21 | const root = ReactDOM.createRoot(document.getElementById('root')); 22 | root.render( 23 | 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Components/globalConfigs.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | 17 | let globalVar = { 18 | "dakshaRecorderStartingPage": 1, 19 | "dakshaRecorderMainPage": 2, 20 | "dakshaRecorderCustomHardwaitPage": 3, 21 | "dakshaRecorderEndPage": 4 22 | } 23 | 24 | 25 | export default globalVar; -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Components/setBadgeForRecording.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | /*global chrome*/ 17 | function setBadgeForRecording() { 18 | return ( 19 | chrome.action.setBadgeBackgroundColor({color:'#F00'} , ()=>{ 20 | chrome.action.setBadgeText({text: 'REC'}) ; 21 | }) 22 | ) 23 | } 24 | 25 | export default setBadgeForRecording -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Components/removeBadgeForRecording.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | /*global chrome*/ 17 | 18 | function removeBadgeForRecording() { 19 | return ( 20 | chrome.action.setBadgeBackgroundColor({color:'#F00'} , ()=>{ 21 | chrome.action.setBadgeText({text: ''}) ; 22 | }) 23 | ) 24 | } 25 | 26 | export default removeBadgeForRecording -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Daksha Recorder", 3 | "description": "Extension for Daksha", 4 | "version": "2.2.1", 5 | "manifest_version": 3, 6 | "action": { 7 | "default_icon":{ 8 | "16": "128.png", 9 | "48": "128.png", 10 | "128": "128.png" 11 | }, 12 | "default_popup": "index.html", 13 | "default_title": "Open the popup" 14 | }, 15 | "background" : { 16 | "service_worker" : "EventPage.js" 17 | }, 18 | "icons": { 19 | "16": "128.png", 20 | "48": "128.png", 21 | "128": "128.png" 22 | }, 23 | "host_permissions": [ 24 | "*://*/*" 25 | ], 26 | "permissions": [ 27 | "tabs", 28 | "storage" 29 | ], 30 | "content_scripts":[ 31 | { 32 | "js": [ 33 | "ContentScript.js" 34 | ], 35 | "matches": ["http://*/*" , "https://*/*"], 36 | "run_at": "document_end", 37 | "all_frames": false 38 | } 39 | ], 40 | "web_accessible_resources": [ 41 | { 42 | "resources": [ "RecordingTab.html"], 43 | "matches": [ "*://*/*" ], 44 | "use_dynamic_url": true 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/RecordingTab.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 | 18 | Document 19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 | 27 |
28 |
29 |

30 | Recording... 31 |

32 |
33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /daksha/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | """ 18 | ASGI config for daksha project. 19 | 20 | It exposes the ASGI callable as a module-level variable named ``application``. 21 | 22 | For more information on this file, see 23 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 24 | """ 25 | 26 | import os 27 | 28 | from django.core.asgi import get_asgi_application 29 | 30 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'daksha.settings') 31 | 32 | application = get_asgi_application() 33 | -------------------------------------------------------------------------------- /daksha/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | """ 18 | WSGI config for daksha project. 19 | 20 | It exposes the WSGI callable as a module-level variable named ``application``. 21 | 22 | For more information on this file, see 23 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 24 | """ 25 | 26 | import os 27 | 28 | from django.core.wsgi import get_wsgi_application 29 | 30 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'daksha.settings') 31 | 32 | application = get_wsgi_application() 33 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/ComponentCss/dakshaRecorderCustomHardwaitPage.css: -------------------------------------------------------------------------------- 1 | .custom-hardwait-main-container{ 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | height: 150px; 6 | width: 215px; 7 | font-size: medium; 8 | user-select: none; 9 | font-family: 'Lato', sans-serif; 10 | } 11 | 12 | .custom-hardwait-input-container{ 13 | display: flex ; 14 | flex-direction: column; 15 | justify-content: space-around; 16 | align-items:center; 17 | height: 120px; 18 | } 19 | #custom-hardwait-inputbox{ 20 | width: 175px; 21 | height: 20px; 22 | border-radius: 7px; 23 | border-color: #0377B3; 24 | border-style: solid; 25 | border-width: thin; 26 | padding: 5px; 27 | } 28 | #custom-hardwait-button{ 29 | border-radius: 7px; 30 | border-color: #0377B3; 31 | border-style: solid; 32 | border-width: thin; 33 | text-align: center; 34 | padding: 5px; 35 | cursor: pointer; 36 | width: 175px; 37 | height: 20px; 38 | } 39 | 40 | .custom-hardwait-back-function{ 41 | width: 20px; 42 | height: 20px; 43 | cursor: pointer; 44 | font-size: 14px; 45 | } 46 | 47 | .custom-hardwait-title-div{ 48 | height: 20px; 49 | } -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/RecordingTab.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | 17 | 18 | window.addEventListener("mousedown", (e) => { 19 | const obj = { x: e.screenX, y: e.screenY, type: 'mousedown' }; 20 | window.parent.postMessage(obj, '*'); 21 | }); 22 | 23 | window.addEventListener("mousemove", (e) => { 24 | const obj = { x: e.screenX, y: e.screenY, type: 'mousemove' }; 25 | window.parent.postMessage(obj, '*'); 26 | }) 27 | window.addEventListener("mouseup", (e) => { 28 | const obj = { x: e.screenX, y: e.screenY, type: 'mouseup' }; 29 | window.parent.postMessage(obj, '*'); 30 | }) 31 | 32 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-extensions", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@webcomponents/custom-elements": "^1.5.0", 10 | "js-yaml": "^4.1.0", 11 | "react": "^18.2.0", 12 | "react-chrome-extension-router": "^1.4.0", 13 | "react-dom": "^18.2.0", 14 | "react-router-dom": "^6.4.2", 15 | "react-scripts": "5.0.1", 16 | "web-vitals": "^2.1.4" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject", 23 | "walk": "webpack --config webpack.config.js && react-scripts build" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | }, 43 | "devDependencies": { 44 | "webpack": "^5.73.0", 45 | "webpack-cli": "^4.10.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /startup_command.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #The reason for this is that Cron sets up a minimalistic environment and doesn't read the environment variables 4 | #that you may have already had set.We solve it by giving cron access to environment variables we need 5 | env >> /etc/environment 6 | 7 | #,, is used for case conversion to lowercase 8 | 9 | if [ "${TEST_RESULT_DB,,}" == "postgres" ] 10 | then 11 | echo "Making Migrations to database" 12 | python manage.py makemigrations engine 13 | echo "=================================" 14 | fi 15 | 16 | if [ "${TEST_RESULT_DB,,}" == "postgres" ] 17 | then 18 | echo "Applying Migrations" 19 | python manage.py migrate 20 | echo "=================================" 21 | fi 22 | 23 | if [ "${CRON_ENABLED,,}" == "true" ] 24 | then 25 | echo "Starting Cron Service" 26 | service cron start 27 | echo "=================================" 28 | fi 29 | 30 | if [ "${CRON_ENABLED,,}" == "true" ] 31 | then 32 | echo "Adding the listed cron jobs" 33 | python manage.py crontab add 34 | echo "=================================" 35 | fi 36 | 37 | if [ "${CRON_ENABLED,,}" == "true" ] 38 | then 39 | echo "The Cron Jobs registered are :-" 40 | python manage.py crontab show 41 | echo "=================================" 42 | fi 43 | 44 | echo "Starting the server" 45 | gunicorn --bind 0.0.0.0:8000 daksha.wsgi 46 | -------------------------------------------------------------------------------- /engine/utils/screenshot_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | import os 18 | from datetime import datetime 19 | from daksha.settings import STORAGE_PATH 20 | 21 | def take_screenshot(test_uuid, test_name, web_driver): 22 | current_time = datetime.utcnow().strftime('%Y-%m-%dT%H_%M_%S.%f')[:-3] 23 | screenshot_dir = f"{STORAGE_PATH}/{test_uuid}/Screenshots/{test_name}" 24 | if not os.path.isdir(screenshot_dir): 25 | os.makedirs(screenshot_dir) 26 | screenshot_file = f"{screenshot_dir}/{current_time}.png" 27 | web_driver.save_screenshot(screenshot_file) 28 | with open(screenshot_file, "rb") as file: 29 | screenshot_data = file.read() 30 | return screenshot_data 31 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Daksha 4 | Copyright (C) 2021 myKaarma. 5 | opensource@mykaarma.com 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | You should have received a copy of the GNU Affero General Public License 15 | along with this program. If not, see . 16 | 17 | """ 18 | """Django's command-line utility for administrative tasks.""" 19 | import os 20 | import sys 21 | 22 | 23 | def main(): 24 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'daksha.settings') 25 | try: 26 | from django.core.management import execute_from_command_line 27 | except ImportError as exc: 28 | raise ImportError( 29 | "Couldn't import Django. Are you sure it's installed and " 30 | "available on your PYTHONPATH environment variable? Did you " 31 | "forget to activate a virtual environment?" 32 | ) from exc 33 | execute_from_command_line(sys.argv) 34 | 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | 3 | ENV PYTHONUNBUFFERED 1 4 | 5 | EXPOSE 8000 6 | 7 | RUN mkdir /daksha 8 | 9 | # Set the working directory to /ui_automation_engine 10 | WORKDIR /daksha 11 | 12 | # Copy the current directory contents into the container at /ui_automation_engine 13 | ADD . /daksha/ 14 | 15 | # Copy the script file for startup 16 | # COPY startup_command.sh /daksha/ 17 | 18 | #gives required premissions 19 | RUN chmod og+x -R /daksha 20 | 21 | # Install any needed packages specified in requirements.txt 22 | RUN pip install -r ./requirements.txt 23 | 24 | #Ubuntu releases are only supported for 9 months. LTS (Long Term Support) releases have support for 5 years. 25 | # Once support is cut for the version you're using, you'll see error messages. 26 | #Ubuntu moves the repositories to another server and the defined URL to reach the sources are no longer available on default location 27 | 28 | #We use the sed command to update the sources in /etc/apt/sources.list file to the new location for 29 | #old package repositories. 30 | #RUN sed -i 's/stable\/updates/stable-security\/updates/' /etc/apt/sources.list 31 | #RUN sed -i 's/stable\/updates/stable-security\/updates/' /etc/apt/sources.list.d/debian.sources 32 | 33 | COPY startup_command.sh /usr/local/bin/startup_command.sh 34 | 35 | RUN chmod +x /usr/local/bin/startup_command.sh 36 | 37 | # Update the package files 38 | RUN apt-get update 39 | 40 | # Install the cron package 41 | RUN apt-get install -y cron 42 | 43 | # start server 44 | CMD ["startup_command.sh"] 45 | -------------------------------------------------------------------------------- /startup_command_mac.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # The reason for this is that Cron sets up a minimalistic environment and doesn't read the environment variables 4 | # that you may have already had set. We solve it by giving cron access to environment variables we need. 5 | export >> ~/.bash_profile 6 | 7 | # Convert TEST_RESULT_DB to lowercase 8 | TEST_RESULT_DB=$(echo "$TEST_RESULT_DB" | tr '[:upper:]' '[:lower:]') 9 | 10 | if [ "$TEST_RESULT_DB" == "postgres" ]; then 11 | echo "Making Migrations to database" 12 | python manage.py makemigrations engine 13 | echo "=================================" 14 | fi 15 | 16 | if [ "$TEST_RESULT_DB" == "postgres" ]; then 17 | echo "Applying Migrations" 18 | python manage.py migrate 19 | echo "=================================" 20 | fi 21 | 22 | # Convert CRON_ENABLED to lowercase 23 | CRON_ENABLED=$(echo "$CRON_ENABLED" | tr '[:upper:]' '[:lower:]') 24 | 25 | if [ "$CRON_ENABLED" == "true" ]; then 26 | echo "Starting Cron Service" 27 | service cron start 28 | echo "=================================" 29 | fi 30 | 31 | if [ "$CRON_ENABLED" == "true" ]; then 32 | echo "Adding the listed cron jobs" 33 | python manage.py crontab add 34 | echo "=================================" 35 | fi 36 | 37 | if [ "$CRON_ENABLED" == "true" ]; then 38 | echo "The Cron Jobs registered are :-" 39 | python manage.py crontab show 40 | echo "=================================" 41 | fi 42 | 43 | echo "Starting the server" 44 | gunicorn --bind 0.0.0.0:8000 daksha.wsgi -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Motivation and Context 8 | 9 | 14 | 15 | ## Types of Changes 16 | 17 | 20 | 21 | - [ ] Bug fix (non-breaking change which fixes an issue) 22 | - [ ] New feature (non-breaking change which adds functionality) 23 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 24 | 25 | ## Checklist 26 | 27 | 32 | 33 | - [ ] I have added tests to cover my changes. 34 | - [ ] My change requires a change to the documentation. 35 | - [ ] I have Extended README or added documentation/comments where applicable 36 | - [ ] Added copyright headers for new files from CONTRIBUTING.md 37 | - [ ] My code follows the code style of this project. 38 | 40 | - [ ] I have read the [contributing guidelines](CONTRIBUTING.md). 41 | - [ ] I have updated the [ChangeLog](CHANGELOG.md) following [Semantic Versioning](https://semver.org) -------------------------------------------------------------------------------- /recorder/extensions/chrome/public/EventPage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | 17 | 18 | //Installing Context Menus at the beginning of chrome Extension Installed. 19 | chrome.runtime.onInstalled.addListener(() => { 20 | //chrome.contextMenus.create(start); 21 | var dakshaYamlObject = {}; 22 | dakshaYamlObject["popupPageNumber"] = 1; 23 | chrome.storage.sync.set(dakshaYamlObject, () => { 24 | }); 25 | var dakshaYamlObject = {}; 26 | dakshaYamlObject["playPauseIcon"] = 1; 27 | chrome.storage.sync.set(dakshaYamlObject, () => { 28 | }); 29 | 30 | var dakshaYamlObject = {}; 31 | dakshaYamlObject["updatePauseValue"] = true; 32 | chrome.storage.sync.set(dakshaYamlObject, () => { 33 | }); 34 | var dakshaYamlObject = {}; 35 | dakshaYamlObject["RecordingCoordinates"] = [1100, 700]; 36 | chrome.storage.sync.set(dakshaYamlObject, () => { 37 | }); 38 | }) 39 | 40 | 41 | -------------------------------------------------------------------------------- /daksha_know-how/ApiRequest.md: -------------------------------------------------------------------------------- 1 | # Daksha Api Request 2 | You can host the API in your local server or at a central server and then hit an API request. You should hit the API request to the URL- ***http://{IP}:{port}/engine/singletest***. For local the port is 8000 and for a central server the port is 8083. 3 | The API request body consists of the following fields- 4 | * **email**-This is the email Id to which the execution status report would be send. 5 | * **test**- This will contain the details of test to be executed 6 | * **source**- You can either load your test case file from local device or from github repository.Depending on your choice it may have either of the 2 values: local or git. 7 | * **type**- This is the type of source, i.e. file/folder. Note that the folder containing test case yml files should not contain any yml files which is not a test case yml file / does not follow the test case yml format 8 | * **path**- This is the location of your test case file(yml format). 9 | * **variales**- This is the dictionary containing the variables to be rendered in the yml file. 10 | 11 | Example- 12 | 13 | ``` 14 | curl --location --request POST 'http://127.0.0.1:8083/daksha/runner' \ 15 | --header 'Content-Type: text/plain' \ 16 | --data-raw '{ 17 | "email": "your.email@mykaarma.com", 18 | "test": { 19 | "source": "local", 20 | "type": "folder", 21 | "path": "/Users/Documents/daksha/examples", 22 | "variables" : { 23 | "username" : "ab@mykaarma", 24 | "password" : "@NoSoupForYou" 25 | } 26 | } 27 | } 28 | ' 29 | ``` 30 | -------------------------------------------------------------------------------- /daksha/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | """daksha URL Configuration 18 | 19 | The `urlpatterns` list routes URLs to views. For more information please see: 20 | https://docs.djangoproject.com/en/3.0/topics/http/urls/ 21 | Examples: 22 | Function views 23 | 1. Add an import: from my_app import views 24 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 25 | Class-based views 26 | 1. Add an import: from other_app.views import Home 27 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 28 | Including another URLconf 29 | 1. Import the include() function: from django.urls import include, path 30 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 31 | """ 32 | 33 | from django.contrib import admin 34 | from django.http import HttpResponse 35 | from django.urls import path, include 36 | from rest_framework import status 37 | 38 | 39 | urlpatterns = [ 40 | path('admin/', admin.site.urls), 41 | path('health', lambda r: HttpResponse('{"status":"up"}', status.HTTP_200_OK)), 42 | path('daksha/', include('engine.urls')), 43 | ] 44 | -------------------------------------------------------------------------------- /daksha_know-how/CronJobsDescription.md: -------------------------------------------------------------------------------- 1 | # Description of Cron Jobs 2 | 3 | You need to write description of Cron jobs in yml file and it should be in the following format - 4 | ``` 5 | crons: 6 | - cron: 7 | params: 8 | email: 9 | test: 10 | source: 11 | type: 12 | path: 13 | variables: 14 | 15 | ``` 16 | A complete test case example is given below along with the description of each field and functionality that we are supporting: 17 | 18 | ``` 19 | crons: 20 | - cron: "*/2 * * * *" 21 | params: 22 | email: abc.123@gmail.com 23 | test: 24 | source: git 25 | type: file 26 | path: examples/loginlogout.yml 27 | variables: 28 | username: ab@mykaarma 29 | password: "@NoSoupForYou" 30 | - cron: "*/3 * * * *" 31 | params: 32 | email: efg.456@mykaarma.com 33 | test: 34 | source: local 35 | type: file 36 | path: examples/sendtext.yml 37 | variables: 38 | username: ab@mykaarma 39 | password: "@NoSoupForYou" 40 | 41 | ' 42 | ``` 43 | 44 | * **crons** 45 | * **cron**-This is the expression defining intervals at which our test will be regulary executed. 46 | * **params** 47 | * **email**- This is the email Id to which the execution status report would be send. 48 | * **test**- This will contain the details of test to be executed 49 | * **source**- You can either load your test case file from local device or from github repository.Depending on your choice it may have either of the 2 values: local or git. 50 | * **type**- This is the type of source, i.e. file/folder 51 | * **path**- This is the location of your test case file(yml format). 52 | * **variables**- This is the dictionary containing the variables to be rendered in the yml file. 53 | 54 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/ComponentCss/dakshaRecorderMainPage.css: -------------------------------------------------------------------------------- 1 | .main-page-container{ 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | height: 200px; 6 | width: 200px; 7 | font-size: medium; 8 | user-select: none; 9 | font-family: 'Lato', sans-serif; 10 | } 11 | 12 | 13 | .main-page-playpause-div{ 14 | display: flex; 15 | flex-direction: row; 16 | justify-content: space-evenly; 17 | user-select: none; 18 | } 19 | .main-page-all-options-container{ 20 | display: flex; 21 | flex-direction: column; 22 | justify-content: space-between; 23 | height: 120px; 24 | } 25 | .main-page-first-option-container{ 26 | display: flex; 27 | flex-direction: row; 28 | 29 | } 30 | .main-page-other-option-div{ 31 | border-radius: 7px; 32 | border-color: #0377B3; 33 | border-width: thin; 34 | border-style: solid; 35 | text-align: center; 36 | padding: 5px; 37 | cursor: pointer; 38 | height: 20px; 39 | } 40 | 41 | #hardwait{ 42 | border-top-left-radius: 7px; 43 | border-bottom-left-radius: 5px; 44 | border-color: #0377B3; 45 | border-width: thin; 46 | text-align: center; 47 | border-style: solid; 48 | width: 90%; 49 | padding: 5px; 50 | cursor: pointer; 51 | height: 20px; 52 | border-right-width: 0px; 53 | } 54 | 55 | #custom-hardwait{ 56 | border-top-right-radius: 7px; 57 | border-bottom-right-radius: 5px; 58 | border-color: #0377B3; 59 | border-width: thin; 60 | text-align: center; 61 | border-style: solid; 62 | width: 10%; 63 | padding: 5px; 64 | cursor: pointer; 65 | height: 20px; 66 | } 67 | 68 | 69 | .main-page-button{ 70 | width: 20px; 71 | height: 20px; 72 | cursor: pointer; 73 | } 74 | 75 | #main-page-right-arrow{ 76 | width: 12px; 77 | height: 12px; 78 | } -------------------------------------------------------------------------------- /engine/method_mapper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | from .selenium_helper import * 19 | from .api_response import make_http_request 20 | 21 | # this mapper maps the action allowed in the yml file to corresponding method that implemented it 22 | 23 | # action: implementing_method 24 | 25 | # Each of the methods listed here must take webdriver and test_uuid as params 26 | 27 | method_map = { 28 | "config": browser_config, 29 | "launch_browser": launch_browser, 30 | "open_url": open_url, 31 | "fill_data": fill_data, 32 | "click_button": click_button, 33 | "select_in_dropdown": select_in_dropdown, 34 | "validate_ui_element": validate_ui_element, 35 | "quit_browser": quit_browser, 36 | "switch_iframe": switch_iframe, 37 | "switch_to_default_iframe": switch_to_default_iframe, 38 | "refresh_page": refresh_page, 39 | "navigate_back": navigate_back, 40 | "open_new_tab": open_new_tab, 41 | "switch_to_tab": switch_to_tab, 42 | "make_HTTP_request": make_http_request, 43 | "wait_for": wait_for, 44 | "capture_ui_element": capture_ui_element, 45 | "scroll_to": scroll_to, 46 | "execute_js": execute_js, 47 | } 48 | -------------------------------------------------------------------------------- /templates/report.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Lato", sans-serif; 3 | } 4 | 5 | .sidenav { 6 | height: 100%; 7 | width: 35%; 8 | position: fixed; 9 | z-index: 1; 10 | top: 0; 11 | left: 0; 12 | background-color: #f1f1f1; 13 | overflow-x: hidden; 14 | padding-top: 20px; 15 | } 16 | 17 | .sidenav a { 18 | padding: 6px 8px 6px 16px; 19 | text-decoration: none; 20 | font-size: 25px; 21 | color: #32CD32; 22 | text-align: center; 23 | display: block; 24 | } 25 | .sidenav c { 26 | padding: 6px 16px 6px 16px; 27 | text-decoration: none; 28 | font-size: 40px; 29 | color: #000; 30 | display: block; 31 | text-align: center; 32 | 33 | } 34 | .sidenav b { 35 | padding: 6px 8px 6px 16px; 36 | text-decoration: none; 37 | font-size: 25px; 38 | color: #FF4500; 39 | display: block; 40 | text-align: center; 41 | } 42 | 43 | .sidenav a:hover { 44 | color: #0000FF; 45 | } 46 | .sidenav b:hover { 47 | color: #0000FF; 48 | } 49 | .sidenav c:hover { 50 | color: #0000FF; 51 | } 52 | 53 | @media screen and (height: 450px) { 54 | .sidenav {padding-top: 15px;} 55 | .sidenav a {font-size: 18px;} 56 | .sidenav b {font-size: 18px;} 57 | .sidenav c {font-size: 18px;} 58 | } 59 | .chart_div { 60 | height: 100%; 61 | width: 100%; 62 | } 63 | .main { 64 | padding-left: 35%; 65 | padding-right: 0%; 66 | width: 79%; 67 | position: fixed; 68 | height: 600px; 69 | margin: 0; 70 | padding-bottom: 20%; 71 | overflow-x: hidden; 72 | overflow-y: scroll; 73 | align-content: flex-start; 74 | } 75 | 76 | 77 | .detail { 78 | color: black; 79 | background-color: #D0D0D0; 80 | width: 81%; 81 | text-align: left; 82 | overflow: visible; 83 | } 84 | .failcard { 85 | 86 | background-color: #B0B0B0; 87 | color: #CC5500; 88 | width: 81%; 89 | font-size: large; 90 | font-weight: bold; 91 | overflow: visible; 92 | text-align: center; 93 | } 94 | 95 | .container { 96 | padding: 24px 36px; 97 | margin: fill; 98 | color: #98FB98; 99 | } -------------------------------------------------------------------------------- /daksha/rp_client_ext.py: -------------------------------------------------------------------------------- 1 | from reportportal_client import RPClient as _RPClient # type: ignore 2 | 3 | class RPClient(_RPClient): 4 | """Extended Report-Portal client. 5 | Adds an optional *launch_uuid* parameter to ``finish_launch`` so you can 6 | explicitly specify which launch to close **without** mutating the 7 | ``launch_id`` attribute on the client (which is read-only in recent 8 | library versions). 9 | """ 10 | 11 | def finish_launch( 12 | self, 13 | *, 14 | end_time, 15 | status=None, 16 | attributes=None, 17 | launch_uuid=None, 18 | **kwargs, 19 | ): # pylint: disable=arguments-differ 20 | # If caller supplies an explicit launch UUID, use it; otherwise fall 21 | # back to the one stored in the client instance. 22 | target_launch = launch_uuid or self.launch_id 23 | 24 | # Safety check 25 | if not target_launch: 26 | from reportportal_client.static.defines import NOT_FOUND # type: ignore 27 | if self.launch_id is NOT_FOUND or not self.launch_id: 28 | # Mirror the original behaviour 29 | return 30 | 31 | # Construct the same request as the base class but using **target_launch** 32 | from reportportal_client.helpers import uri_join # type: ignore 33 | from reportportal_client.core.rp_requests import LaunchFinishRequest # type: ignore 34 | from reportportal_client.core.rp_requests import HttpRequest # type: ignore 35 | 36 | url = uri_join(self.base_url_v2, "launch", target_launch, "finish") 37 | request_payload = LaunchFinishRequest( 38 | end_time, 39 | status=status, 40 | attributes=attributes, 41 | description=kwargs.get("description"), 42 | ).payload 43 | 44 | response = HttpRequest( 45 | self.session.put, 46 | url=url, 47 | json=request_payload, 48 | verify_ssl=self.verify_ssl, 49 | name="Finish Launch", 50 | ).make() 51 | 52 | if not response: 53 | return 54 | 55 | return response.message -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Components/dakshaRecorderStartingPage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | /*global chrome*/ 17 | import React from 'react' 18 | import '../ComponentCss/dakshaRecorderStartingPage.css'; 19 | import setBadgeForRecording from './setBadgeForRecording'; 20 | import GlobalVariables from "./globalConfigs"; 21 | let dakshaRecorderMainPage = GlobalVariables.dakshaRecorderMainPage; 22 | 23 | function DakshaRecorderStartingPage(props) { 24 | 25 | return ( 26 | <> 27 |
28 |
29 | Daksha Recorder 30 |
31 |
{ 32 | props.setState(dakshaRecorderMainPage); 33 | let url = ""; 34 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 35 | url = tabs[0].url; 36 | let obj = { 37 | "type": "start", 38 | "msg": url 39 | } 40 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 41 | setBadgeForRecording(); 42 | }); 43 | 44 | }}> 45 | START RECORDING 46 |
47 |
48 | 49 | ) 50 | } 51 | 52 | export default DakshaRecorderStartingPage; -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to Daksha 2 | =============================================== 3 | 4 | ------------------------------------------------------------------------------ 5 | 6 | Thank you for taking the time to contribute! Please follow the following guidelines while contributing to this project. 7 | 8 | ## General: 9 | - If it's a major feature/change please create an [issue](https://github.com/mykaarma/daksha/issues) and discuss with project members. 10 | - Create a fork of Daksha on github (http://help.github.com/fork-a-repo/) and initiate a pull request to [main](https://github.com/mykaarma/daksha/tree/main) branch for submitting your change/feature. 11 | 12 | 13 | ## Python Code Quality Standards: 14 | - All code should compile and run for **Python 3.8**. 15 | - Maximum line length is 120 characters. 16 | - Indentation should be made with 4 spaces, not tabs. 17 | - For any non-trivial code, include comments. 18 | - Follow the logging conventions used in the rest of the project. 19 | - Tests should be included for any new functionality being added. 20 | 21 | ## License 22 | 23 | By contributing your code, you agree to license your contribution under the terms of the AGPLv3: [LICENSE](LICENSE) 24 | 25 | All files are released with the AGPL v3 license. 26 | 27 | If you are adding a new file it should have a header like this: 28 | 29 | ``` 30 | """ 31 | Daksha 32 | Copyright (C) 2021 myKaarma. 33 | opensource@mykaarma.com 34 | This program is free software: you can redistribute it and/or modify 35 | it under the terms of the GNU Affero General Public License as published by 36 | the Free Software Foundation, either version 3 of the License, or 37 | (at your option) any later version. 38 | This program is distributed in the hope that it will be useful, 39 | but WITHOUT ANY WARRANTY; without even the implied warranty of 40 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 41 | GNU Affero General Public License for more details. 42 | You should have received a copy of the GNU Affero General Public License 43 | along with this program. If not, see . 44 | 45 | """ 46 | ``` 47 | 48 | 49 | ## Questions 50 | 51 | If you have questions or want to report a bug please create an [Issue]( https://github.com/mykaarma/daksha/issues ) 52 | -------------------------------------------------------------------------------- /engine/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | import copy 18 | 19 | from django.db import models 20 | 21 | 22 | # Create your models here. 23 | class TestExecutor: 24 | def __init__(self, index, test_uuid, variable_dict, test_yml, web_driver , test_result, report_portal_service=None, report_portal_test_id=None): 25 | self.index = index 26 | self.test_uuid = test_uuid 27 | self.variable_dictionary = copy.deepcopy(variable_dict) 28 | self.test_yml = test_yml 29 | self.web_driver = web_driver 30 | self.test_result=test_result 31 | self.report_portal_service=report_portal_service 32 | self.report_portal_test_id=report_portal_test_id 33 | 34 | class TestResult: 35 | def __init__(self, name, test_status, step, failure_reason): 36 | self.test_name = name 37 | self.test_status = test_status 38 | self.failed_step = step 39 | self.failure_cause = failure_reason 40 | 41 | class GetTestResultsResponse: 42 | def __init__(self,testresults,errors): 43 | self.testresults=testresults 44 | self.errors=errors 45 | 46 | class TestResults(models.Model): 47 | TestUUID=models.TextField(max_length=11) 48 | TestName=models.TextField() 49 | Status=models.TextField() 50 | FailureStep=models.TextField() 51 | FailureCause=models.TextField() 52 | InsertTs = models.DateTimeField(auto_now_add=True) #The values updated here are following UTC time zone 53 | UpdateTs = models.DateTimeField(auto_now=True) #The values updated here are following UTC time zone 54 | VariableDictionary=models.TextField() -------------------------------------------------------------------------------- /engine/test_result_utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | import json 19 | from .logs import * 20 | from daksha import settings 21 | from engine import models 22 | 23 | def initialize_test_result(test_uuid,test_yml): 24 | test_result=models.TestResults() 25 | if(settings.TEST_RESULT_DB != None and settings.TEST_RESULT_DB.lower() == "postgres"): 26 | test_name=test_yml["name"] 27 | test_result.TestUUID=test_uuid 28 | test_result.TestName=test_name 29 | test_result.Status="Waiting" 30 | test_result.save() 31 | logger.info(f"Initialized test named {test_name} with TestUUID {test_uuid} in the database") 32 | 33 | else : 34 | logger.info("The Test results would not be saved in database as that functionality is not opted for") 35 | return test_result 36 | 37 | 38 | def save_result_in_db(test_executor,execution_result,step,error_stack): 39 | test_name=test_executor.test_yml["name"] 40 | testUUID=test_executor.test_uuid 41 | if execution_result: 42 | test_executor.test_result.Status="Passed" 43 | test_executor.test_result.VariableDictionary=json.dumps(test_executor.variable_dictionary) 44 | else: 45 | test_executor.test_result.FailureStep=str(step)[0:200] 46 | test_executor.test_result.FailureCause=str(error_stack)[0:200] 47 | test_executor.test_result.Status="Failed" 48 | test_executor.test_result.VariableDictionary=json.dumps(test_executor.variable_dictionary) 49 | 50 | test_executor.test_result.save() 51 | logger.info(f"The Test Result for test named {test_name} of TestUUId {testUUID} have been updated in the database") -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | engine-apache: 5 | image: httpd:2.4-alpine 6 | container_name: engine-apache 7 | restart: always 8 | volumes: 9 | - ./test-data:/usr/local/apache2/htdocs/ 10 | ports: 11 | - "8485:80" 12 | 13 | web: 14 | build: . 15 | container_name: daksha 16 | restart: always 17 | volumes: 18 | - ./test-data:/daksha/test-data 19 | ports: 20 | - "8083:8000" 21 | environment: 22 | STORAGE_PATH: "test-data" 23 | APACHE_URL: "http://127.0.0.1:8485/" 24 | 25 | #These variables are only needed if you want to store the test results in the database 26 | TEST_RESULT_DB: "postgres" 27 | PG_DB : "postgres" 28 | PG_USER : "postgres" 29 | PG_HOST: "postgresdb" 30 | PG_PASSWORD: "postgres" 31 | PG_PORT: "5432" 32 | 33 | #Set these environment variables if you want Cron functionality 34 | # CRON_ENABLED : "false" 35 | # CRON_FILE_SOURCE : none 36 | # CRON_FILE_PATH : none 37 | 38 | #Set these environment variables if you want Test reports to be shown in Report Portal 39 | # REPORT_PORTAL_ENABLED : "True" 40 | # REPORT_PORTAL_ENDPOINT: ${REPORT_PORTAL_ENDPOINT} 41 | # REPORT_PORTAL_PROJECT_NAME: ${REPORT_PORTAL_PROJECT_NAME} 42 | # REPORT_PORTAL_TOKEN: ${REPORT_PORTAL_TOKEN} 43 | 44 | # If you want don't want database enabled, then comment out the below lines 45 | depends_on: 46 | - postgresdb 47 | 48 | selenium-hub: 49 | image: selenium/hub 50 | ports: 51 | - "4444:4444" 52 | 53 | selenium-node: 54 | image: selenium/node-chrome 55 | environment: 56 | - HUB_PORT=4444 57 | - SE_EVENT_BUS_HOST=selenium-hub 58 | - TZ=PST 59 | - SE_EVENT_BUS_PUBLISH_PORT=4442 60 | - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 61 | - SE_NODE_MAX_INSTANCES=4 62 | - SE_NODE_MAX_SESSIONS=5 63 | - NODE_MAX_INSTANCES=5 64 | - NODE_MAX_SESSION=5 65 | - SE_NODE_GRID_URL=http://selenium-hub:4444/wd/hub 66 | depends_on: 67 | - selenium-hub 68 | 69 | postgresdb: 70 | container_name: postgresdb 71 | image: postgres:12 72 | environment: 73 | - POSTGRES_USER=postgres 74 | - POSTGRES_PASSWORD=postgres 75 | - POSTGRES_DB=postgres 76 | ports: 77 | - "5432:5432" 78 | volumes: 79 | - ./test-data/db-data:/var/lib/postgresql/data 80 | 81 | 82 | -------------------------------------------------------------------------------- /engine/email_generator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | from daksha.settings import EMAIL_HOST_USER, POSTMARK_TOKEN, APACHE_URL, STORAGE_PATH 19 | from .logs import * 20 | from postmarker.core import PostmarkClient 21 | 22 | 23 | def send_report_email(test_uuid, report_url, recipient_email): 24 | """ 25 | Sends email to the recipient email provided 26 | :param test_uuid: ID of the Test 27 | :type test_uuid: str 28 | :param report_url: The url where the report is hoisted 29 | :type report_url: str 30 | :param recipient_email: The email where the report will be sent 31 | :type recipient_email: str 32 | """ 33 | 34 | subject = "Report for Test ID: " + test_uuid 35 | 36 | if len(APACHE_URL) == 0: 37 | test_folder = STORAGE_PATH + '/' + test_uuid + '/' 38 | message = "Please open this folder on your system to get the test Report : '" + test_folder + "'" 39 | else: 40 | folder_url = APACHE_URL + test_uuid + '/' 41 | message = "Please open this url to get your test Report : " + report_url + "\nTest folder url: " + folder_url 42 | # message.attach_file('/templates/testPage.html') 43 | logger.info(message) 44 | if len(POSTMARK_TOKEN) == 0: 45 | logger.info('Report not emailed, Postmark token not set') 46 | else: 47 | send_email_postmark(subject, message, recipient_email) 48 | logger.info('Email sent to ' + recipient_email) 49 | 50 | 51 | def send_email_postmark(subject, message, recipient_email): 52 | postmark = PostmarkClient(server_token=POSTMARK_TOKEN) 53 | postmark.emails.send( 54 | From=EMAIL_HOST_USER, 55 | To=recipient_email, 56 | Subject=subject, 57 | HtmlBody=message 58 | ) 59 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/ReadMe.md: -------------------------------------------------------------------------------- 1 | Daksha Recorder 2 | =============================================== 3 | 4 | ## Introduction 5 | Daksha Recorder is a chrome Extension based on javascript. It is used to record live user actions and create a YAML file which can used in [Daksha](https://github.com/mykaarma/daksha). This tool reduces the time consumed in creating the yml file and makes the process hassle free.User can download the yaml in his local and change the default config values to use in [Daksha](https://github.com/mykaarma/daksha). 6 | 7 | ## Create a test YAML file using the Daksha Recorder 8 | 9 | - Open chrome web store and add [Daksha Recorder Extension](https://chrome.google.com/webstore/detail/daksha-recorder/gmpmpceenkghjdlelhgepnknlijllfom?utm_source=ext_sidebar&hl=en-GB) to your chrome. 10 | - Please refer to this [video recording](https://youtu.be/4FRdS2iJZoQ?t=986) for creating a test.yml using Daksha Recorder. 11 | - Download the test YAML (HelloWorld.yml) from Daksha Recorder. 12 | - The following configs are user specific , so the recorder keeps them empty.You will need to edit the downloaded YAML file to add the following values. 13 | ``` 14 | config: 15 | env: local 16 | browser: chrome 17 | driverAddress: http://selenium-hub:4444/wd/hub 18 | name: HelloWorld 19 | ``` 20 | Here is an exapmle test YAML file- HelloWorld.yml 21 | - Move this file to the `test-data` directory created in previous step. 22 | 23 | ## Building(For Development) 24 | This extension is published in chrome web store, however if you want to make some tweaks to the extension, you can follow the below guidelines 25 | - Clone the repository in your local environment 26 | - Extract all files 27 | - Install [Node.js](https://nodejs.org/en/download/) 28 | - In any terminal, open the folder with name "ChromeExtension" which is present in the extracted folder 29 | - In Terminal, Write following commands: 30 | * npm install 31 | * npm start 32 | ## Running 33 | - In google chrome, visit [chrome Extensions](chrome://extensions/) 34 | - Enable Developer Mode 35 | - click on Load Unpacked Button 36 | - Select the folder with name "ChromeExtension" which is repesent in cloned repo. 37 | - Enable the Extension and you are good to go ahead. 38 | ## Configuration Variables in Yml file 39 | To know about these variables, you can refer [How-to-set-variables](https://github.com/mykaarma/daksha/blob/main/daksha_know-how/CreateTest.md) 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | */migrations/ 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | .python-version 87 | 88 | # pipenv 89 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 90 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 91 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 92 | # install all needed dependencies. 93 | #Pipfile.lock 94 | 95 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | # Pycharm 133 | .idea/ 134 | .DS_Store 135 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # `DAKSHA` Change Log 2 | 3 | *version*: `2.11.1` 4 | 5 | ## v 2.11.1 6 | 1. Bug Fix: Tests were not getting marked as finished in Report Portal. 7 | 8 | ## v 2.10.3 9 | 1. Created Quickstart procedure to ensure smooth execution of first test by users. 10 | 11 | ## v 2.10.2 12 | 1. Fixed bug related to item_type of Tests uploaded to Report Portal. 13 | 14 | ## v 2.10.1 15 | 1. Fixed bug related to labels displayed in Report Portal. 16 | 17 | ## v 2.10.0 18 | 1. Added Report Portal functionality to display Daksha Test Reports. 19 | 20 | ## v 2.9.0 21 | 1. Modified GET endpoint to include test name as query params. 22 | 23 | ## v 2.8.0 24 | 1. Added startup_command.bat for starting the app locally in Windows. 25 | 26 | ## v 2.7.0 27 | 1. Added the Variable Dictionary in the test result table of postgres database. 28 | 2. Changed request method to retrieve data to POST. 29 | 30 | ## v 2.6.1 31 | 1. Started the server using gunicorn. 32 | 33 | ## v 2.6.0 34 | 1. Added startup command in entrypoint. 35 | 36 | ## v 2.5.0 37 | 1. Added minor imporvement in startup command for db. 38 | 39 | ## v 2.4.1 40 | 1. Modified permissions and fixed initialisation issues with cron jobs. 41 | 42 | ## v 2.4.0 43 | 1. Created a GET endpoint to recieve test result data by providing TestUUID. 44 | 45 | ## v 2.3.0 46 | 1. Added Cron Job functionality to run tests in regulated intervals. 47 | 48 | ## v 2.2.2 49 | 1. Fixed server initialisation issue in case of running daksha server in local with no database. 50 | 51 | ## v 2.2.1 52 | 1. Added In_Progress status for tests in database. 53 | 54 | ## v 2.2.0 55 | 1. Added Database for Test Results in Daksha. 56 | 2. Users can opt for test results to be saved in a Postgres database. 57 | 58 | ## v 2.1.3 59 | 1. Adressed issue #47 60 | 61 | ## v 2.1.2 62 | 1. Added UI functionality to Daksha Chrome Extension. 63 | 64 | ## v 1.1.0 65 | 1. Added chrome extension for autogenerating Daksha Yaml 66 | 67 | ## v 1.0.1 68 | 1. Added scroll_to functionality in helper methods 69 | 70 | ## v 1.0.0 71 | 1. Adressed issues #11 , #15 , #19 , #29 72 | 73 | ## v 0.3.2 74 | 1. Changed python version to 3.10 in Dockerfile 75 | 76 | ## v 0.3.1 77 | 1. Fixed dependabot alerts 78 | 79 | ## v 0.3.0 80 | 1. Added Alert Sending feature on test failure 81 | 82 | ## v 0.2.2 83 | 1. Fixed open_new_tab method 84 | 85 | ## v 0.2.1 86 | 1. Added wait method 87 | 2. Fixed switch_iframe method 88 | 89 | ## v 0.2.0 90 | 1. Added API call functionality 91 | 92 | ## v 0.1.0 93 | 1. Added variable support in yaml 94 | 2. Added variable_dictionary to engine 95 | 96 | ## v 0.0.1 97 | 1. Initial release 98 | -------------------------------------------------------------------------------- /engine/alert_sender.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | from daksha.settings import ALERT_URL 18 | from .logs import * 19 | import requests 20 | from string import Template 21 | 22 | 23 | def gchat_alert(test_uuid, name, step, error_stack): 24 | alert_body = "Test Case: " + name + " failed for test id : " + test_uuid + "\n _ERROR_ : ```" + error_stack.replace( 25 | '"', "") + "```" + "\n in _step_ ```" + step.replace('"', "") + "```" 26 | if len(ALERT_URL) == 0: 27 | logger.info('Alert not sent, ALERT_URL not set') 28 | return 29 | else: 30 | alert_template = Template(open("templates/gchat_alert.json", "r").read()) 31 | return requests.post(ALERT_URL, data=alert_template.substitute(body=alert_body)) 32 | 33 | 34 | def slack_alert(test_uuid, name, step, error_stack): 35 | alert_body = "Test Case: " + name + " failed for test id : " + test_uuid + "\n _ERROR_ : ```" + error_stack.replace( 36 | '"', "") + "```" + "\n in _step_ ```" + step.replace('"', "") + "```" 37 | headers = {'content-type': 'application/json'} 38 | if len(ALERT_URL) == 0: 39 | logger.info('Alert not sent, ALERT_URL not set') 40 | return 41 | else: 42 | alert_template = Template(open("templates/slack_alert.json", "r").read()) 43 | return requests.post(ALERT_URL, data=alert_template.substitute(body=alert_body), headers=headers) 44 | 45 | 46 | switcher = { 47 | "gchat": gchat_alert, 48 | "slack": slack_alert 49 | } 50 | 51 | 52 | def send_alert(test_uuid, name, step, error_stack, alert_type): 53 | 54 | func = switcher.get(alert_type, "no_alert") 55 | if (alert_type == None) or (len(alert_type) == 0): 56 | return 57 | elif func == "no_alert": 58 | logger.warn("Supported alert type not found,alert types supported: " + str(list(switcher.keys()))) 59 | else: 60 | return func(test_uuid, name, step, error_stack) 61 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Components/dakshaRecorderCustomHardwaitPage.js: -------------------------------------------------------------------------------- 1 | /*global chrome*/ 2 | /* 3 | Daksha 4 | Copyright (C) 2021 myKaarma. 5 | opensource@mykaarma.com 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | You should have received a copy of the GNU Affero General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | import React, { useState } from 'react'; 19 | import '../ComponentCss/dakshaRecorderCustomHardwaitPage.css'; 20 | import GlobalVariables from './globalConfigs'; 21 | let dakshaRecorderMainPage = GlobalVariables.dakshaRecorderMainPage ; 22 | function DakshaRecorderCustomHardwaitPage(props) { 23 | const [val, setval] = useState(); 24 | const func = (value) => { 25 | if (value >= 0) { 26 | setval(value); 27 | } 28 | } 29 | return ( 30 | <> 31 |
32 |
props.setState(dakshaRecorderMainPage)}> 33 | Back 34 |
35 |
36 | 37 |
38 | CUSTOM HARD WAIT 39 |
40 | 41 |
42 | func(e.target.value)} /> 43 |
44 |
{ 45 | props.setState(2); 46 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 47 | let obj = { 48 | "type": "customSecondsWait", 49 | "sec": val 50 | } 51 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 52 | }) 53 | }}> 54 | ADD HARD WAIT 55 |
56 |
57 |
58 | 59 | ) 60 | } 61 | 62 | export default DakshaRecorderCustomHardwaitPage; -------------------------------------------------------------------------------- /daksha_know-how/QuickStart.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | Welcome to Daksha! This quick start guide will walk you through the process of running your first testcase using Daksha. Follow these three main steps to get started: 4 | 5 | 1. Run the Daksha Server. 6 | 2. Create a `test.yml` using the Daksha Recorder. 7 | 3. Send an API request to Daksha. 8 | 9 | ## Using Docker (Recommended for Users) 10 | 11 | ### Run Daksha Server 12 | 13 | - Open your terminal or command prompt based on your operating system. 14 | - Clone the [Daksha Repository](https://github.com/mykaarma/daksha): 15 |
```git clone https://github.com/mykaarma/daksha.git``` 16 | - Navigate to the directory where you cloned Daksha and open a terminal here. 17 |
```cd daksha``` 18 | - Install Docker Desktop. Refer [Docker Documentation](https://docs.docker.com/compose/install/) 19 | - Start the Docker engine. 20 | - Create a test-data directory. 21 |
```mkdir test-data``` 22 | - Inside the test-data directory create a db-data directory. The database files will reside in this directory. 23 |
```mkdir test-data/db-data``` 24 | - Run the command `docker-compose up -d` to initiate the build and deploy the project. 25 | For macOS the command will be `docker compose up -d`. 26 | - You now have the daksha server running. 27 | 28 | ### Create a test YAML file using the Daksha Recorder 29 | 30 | - Open chrome web store and add [Daksha Recorder Extension](https://chrome.google.com/webstore/detail/daksha-recorder/gmpmpceenkghjdlelhgepnknlijllfom?utm_source=ext_sidebar&hl=en-GB) to your chrome. 31 | - Please refer to this [video recording](https://youtu.be/4FRdS2iJZoQ?t=986) for creating a test.yml using Daksha Recorder. 32 | - Download the test YAML (HelloWorld.yml) from Daksha Recorder. 33 | - The following configs are user specific , so the recorder keeps them empty.You will need to edit the downloaded YAML file to add the following values. 34 | ``` 35 | config: 36 | env: local 37 | browser: chrome 38 | driverAddress: http://selenium-hub:4444/wd/hub 39 | name: HelloWorld 40 | ``` 41 | Here is an exapmle test YAML file- HelloWorld.yml 42 | - Move this file to the `test-data` directory created in previous step. 43 | 44 | ### Hit API Request 45 | 46 | - Copy the following curl request. Replace the email field and path field in the curl.
Path should be `/test-data/{your file name}`. This is the path of the test.yml that you had copied in the test-data directory and mounted in docker. 47 | ``` 48 | curl --location --request POST 'http://127.0.0.1:8083/daksha/runner' \ 49 | --header 'Content-Type: text/plain' \ 50 | --data-raw '{ 51 | "email": "your.email@mykaarma.com", 52 | "test": { 53 | "source": "local", 54 | "type": "file", 55 | "path": "/daksha/test-data/HelloWorld.yml", 56 | "variables": "" 57 | } 58 | }' 59 | 60 | ``` 61 | 62 | ### See what is happening inside the test 63 | 64 | - Open this url http://localhost:4444/ui#/sessions 65 | - In sessions you will see active session being generated on hitting the API request. 66 | - Click on the video icon and enter password `secret`. You will be able to see what the steps being executed in chrome. 67 | -------------------------------------------------------------------------------- /examples/API_support.yml: -------------------------------------------------------------------------------- 1 | config: 2 | env: local 3 | browser: chrome 4 | driverAddress: D:\Recover\chromedriver_win32\chromedriver.exe 5 | # driverAddress: http://192.168.21.185:4444/wd/hub 6 | name: APISupportTestExample 7 | task: 8 | - make_HTTP_request: 9 | request: 'POST' 10 | url : 'https://fakerestapi.azurewebsites.net/api/v1/Activities' 11 | headers : 12 | accept: text/plain; v=1.0 13 | Content-Type: application/json; v=1.0 14 | data: ' 15 | { 16 | "id": 0, 17 | "title": "string", 18 | "dueDate": "2021-06-23T18:03:18.689Z", 19 | "completed": true 20 | }' 21 | response: 22 | 'status' : 200 #first check the response and then we will move ahead 23 | save: #user need to pass this save key and the values must be passed as lists 24 | - 'key' : "title" 25 | 'save in' : title 26 | - make_HTTP_request: 27 | request : 'GET' 28 | url : 'https://httpbin.org/basic-auth/{{username}}/{{passwd}}' 29 | headers: 30 | accept : 'application/json' 31 | auth: 32 | type : "Basic" #can be Basic, Digest and Proxy 33 | username : "{{ username}}" 34 | password : "{{ passwd }}" 35 | Cookies: 36 | custom_cookie : 37 | 'cookie_name': 'cookie_value' 38 | timeout: 12 #it can be integer or tuple 39 | response: 40 | 'status' : 200 #after making the http request, first check the response and then we will move ahead 41 | save: #user need to pass this save key and the values must be passed as lists 42 | - 'save in' : response_get_body 43 | - make_HTTP_request: 44 | request: 'PUT' 45 | url : 'https://fakerestapi.azurewebsites.net/api/v1/Activities/{{id}}' 46 | headers : 47 | accept: text/plain; v=1.0 48 | Content-Type: application/json; v=1.0 49 | payload: '{ 50 | "id": 1, 51 | "title": "string", 52 | "dueDate": "2021-06-14T03:56:36.635Z", 53 | "completed": true 54 | }' 55 | # response: 56 | # 'status' : 200 #no status code provided 57 | - make_HTTP_request: 58 | request: 'POST' 59 | url : 'https://httpbin.org/post' 60 | headers : 61 | 'accept' : 'application/json' 62 | raw-data: > 63 | { 64 | "form": { 65 | "comments": "fast", 66 | "custemail": "rg@gmail.com", 67 | "custname": "Rg", 68 | "custtel": "783849", 69 | "delivery": "18:30", 70 | "size": "medium", 71 | "topping": [ 72 | "cheese", 73 | "onion", 74 | "mushroom" 75 | ] 76 | } 77 | } 78 | response: 79 | 'status' : 200 #after making the http request, first check the response and then we will move ahead 80 | - make_HTTP_request: 81 | request: 'DELETE' 82 | url : 'https://httpbin.org/delete' 83 | headers : 84 | 'accept' : 'application/json' 85 | response: 86 | 'status' : 201 #status mismatch 87 | save: #user need to pass this save key and the values must be passed as lists 88 | - 'save in' : response_delete_body -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/App.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | /*global chrome*/ 17 | 18 | import * as React from 'react'; 19 | import { useState, useEffect } from 'react'; 20 | import DakshaRecorderStartingPage from './Components/dakshaRecorderStartingPage'; 21 | import DakshaRecorderMainPage from './Components/dakshaRecorderMainPage'; 22 | import DakshaRecorderCustomHardwaitPage from './Components/dakshaRecorderCustomHardwaitPage'; 23 | import DakshaRecorderEndPage from './Components/dakshaRecorderEndPage'; 24 | import GlobalVariables from './Components/globalConfigs'; 25 | let dakshaRecorderStartingPage = GlobalVariables.dakshaRecorderStartingPage; 26 | let dakshaRecorderMainPage = GlobalVariables.dakshaRecorderMainPage; 27 | let dakshaRecorderCustomHardwaitPage = GlobalVariables.dakshaRecorderCustomHardwaitPage; 28 | let dakshaRecorderEndPage = GlobalVariables.dakshaRecorderEndPage; 29 | let pageNumber; 30 | chrome.storage.sync.get("popupPageNumber", function (result) { 31 | pageNumber = result["popupPageNumber"]; 32 | var dakshaYamlObject = {}; 33 | dakshaYamlObject["popupPageNumber"] = pageNumber; 34 | chrome.storage.sync.set(dakshaYamlObject, () => { 35 | }); 36 | }); 37 | 38 | let iconChanger; 39 | chrome.storage.sync.get("playPauseIcon", function (result) { 40 | iconChanger = result["playPauseIcon"]; 41 | }); 42 | 43 | const MainComponent = () => { 44 | 45 | const [state, setState] = useState(pageNumber); 46 | 47 | useEffect(() => { 48 | var dakshaYamlObject = {}; 49 | dakshaYamlObject["popupPageNumber"] = state; 50 | chrome.storage.sync.set(dakshaYamlObject, () => { 51 | }); 52 | }, [state]); 53 | 54 | const [image, changeImage] = useState(iconChanger); 55 | 56 | useEffect(() => { 57 | var dakshaYamlObject = {}; 58 | dakshaYamlObject["playPauseIcon"] = image; 59 | chrome.storage.sync.set(dakshaYamlObject, () => { 60 | }); 61 | }, [image]); 62 | 63 | if (state == dakshaRecorderStartingPage) 64 | return ( 65 | 66 | 67 | ) 68 | else if (state == dakshaRecorderMainPage) 69 | return ( 70 | 71 | ) 72 | else if (state == dakshaRecorderCustomHardwaitPage) 73 | return ( 74 | 75 | ) 76 | else if (state == dakshaRecorderEndPage) 77 | return ( 78 | 79 | ) 80 | }; 81 | 82 | const App = () => { 83 | return ( 84 | 85 | ); 86 | }; 87 | export default App; 88 | -------------------------------------------------------------------------------- /engine/logs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | import logging 18 | import os 19 | import threading 20 | 21 | from reportportal_client.helpers import timestamp 22 | from daksha.settings import LOG_FILE,REPORT_PORTAL_ENABLED,report_portal_service 23 | 24 | os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True) 25 | 26 | # Logging configs 27 | logFormatter = logging.Formatter('%(asctime)s [%(levelname)-7.7s] %(message)s') 28 | logger = logging.getLogger() 29 | logger.setLevel(logging.INFO) 30 | 31 | fileHandler = logging.FileHandler(LOG_FILE) 32 | fileHandler.setFormatter(logFormatter) 33 | logger.addHandler(fileHandler) 34 | 35 | consoleHandler = logging.StreamHandler() 36 | consoleHandler.setFormatter(logFormatter) 37 | logger.addHandler(consoleHandler) 38 | 39 | if(REPORT_PORTAL_ENABLED != None and REPORT_PORTAL_ENABLED.lower() == "true"): 40 | class ReportPortalLoggingHandler(logging.Handler): 41 | def __init__(self): 42 | super().__init__() 43 | self.thread_local = threading.local() 44 | #threading.local() is used to create a unique thread-local object 45 | 46 | def set_item_id(self, item_id): 47 | self.thread_local.item_id = item_id 48 | 49 | def clear_item_id(self): 50 | if hasattr(self.thread_local, 'item_id'): 51 | del self.thread_local.item_id 52 | 53 | def set_service(self, service): 54 | self.thread_local.service = service 55 | 56 | def clear_service(self): 57 | if hasattr(self.thread_local, 'service'): 58 | del self.thread_local.service 59 | 60 | 61 | def emit(self, record): 62 | 63 | report_portal_service = getattr(self.thread_local, 'service', None) 64 | item_id = getattr(self.thread_local, 'item_id', None) 65 | 66 | if report_portal_service is not None and item_id is not None: 67 | msg = self.format(record) 68 | level = record.levelname 69 | if level == "WARNING": 70 | level = "WARN" 71 | 72 | screenshot = record.__dict__.get('screenshot') 73 | if screenshot: 74 | attachment = {"data": screenshot, "mime": "image/png"} 75 | report_portal_service.log( 76 | time=timestamp(), 77 | message=msg, 78 | level=level, 79 | item_id=item_id, 80 | attachment=attachment 81 | ) 82 | else: 83 | report_portal_service.log( 84 | time=timestamp(), 85 | message=msg, 86 | level=level, 87 | item_id=item_id 88 | ) 89 | 90 | report_portal_logging_handler = ReportPortalLoggingHandler() 91 | logger.addHandler(report_portal_logging_handler) 92 | 93 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Components/dakshaRecorderEndPage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty tof 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | 17 | /*global chrome*/ 18 | import React from "react"; 19 | import '../ComponentCss/dakshaRecorderEndPage.css'; 20 | import setBadgeForRecording from "./setBadgeForRecording"; 21 | import GlobalVariables from "./globalConfigs"; 22 | let dakshaRecorderStartingPage = GlobalVariables.dakshaRecorderStartingPage; 23 | let dakshaRecorderMainPage = GlobalVariables.dakshaRecorderMainPage; 24 | 25 | function DakshaRecorderEndPage(props) { 26 | return ( 27 | <> 28 |
29 |
30 | Daksha Recorder 31 |
32 |
33 |
{ 34 | props.setState(dakshaRecorderStartingPage); 35 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 36 | let obj = { 37 | "type": "download" 38 | } 39 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 40 | }) 41 | }}> 42 | DOWNLOAD 43 |
44 |
{ 45 | props.setState(dakshaRecorderStartingPage); 46 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 47 | let obj = { 48 | "type": "copy_to_clipboard" 49 | } 50 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 51 | }) 52 | }}> 53 | COPY TO CLIPBOARD 54 |
55 |
{ 56 | props.changeImage(1); 57 | props.setState(dakshaRecorderMainPage); 58 | let url = ""; 59 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 60 | url = tabs[0].url; 61 | let obj = { 62 | "type": "start", 63 | "msg": url 64 | } 65 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 66 | setBadgeForRecording(); 67 | }) 68 | }}> 69 | START RECORDING 70 |
71 |
72 |
73 | 74 | ) 75 | } 76 | 77 | 78 | 79 | export default DakshaRecorderEndPage; -------------------------------------------------------------------------------- /engine/thread_executor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2022 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty tof 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | """ 16 | 17 | from concurrent.futures.thread import ThreadPoolExecutor 18 | from engine.executor import execute_test 19 | from .email_generator import send_report_email 20 | from .logs import * 21 | from daksha.settings import APACHE_URL,TEST_RESULT_DB, REPORT_PORTAL_ENABLED,report_portal_service 22 | from .models import TestExecutor 23 | from .testreport_generator import generate_report 24 | from engine import test_result_utils 25 | from reportportal_client.helpers import timestamp 26 | 27 | def thread_executor(test_ymls, initial_variable_dictionary, test_uuid, email): 28 | # Test Executor object initialization and store in a list 29 | testExecutorObjects=[] 30 | 31 | if(REPORT_PORTAL_ENABLED != None and REPORT_PORTAL_ENABLED.lower() == "true"): 32 | report_portal_service.start() 33 | logger.info("User has opted for Test Reports to be displayed on Report Portal") 34 | launch_id = report_portal_service.start_launch(name = f"Daksha_test_{test_uuid}", mode = 'DEFAULT', start_time = timestamp()) 35 | logger.info(f"Initiating launch in Report Portal with name {test_uuid}") 36 | 37 | for test_yml in test_ymls: 38 | test_result_object = test_result_utils.initialize_test_result(test_uuid, test_yml) 39 | if(REPORT_PORTAL_ENABLED != None and REPORT_PORTAL_ENABLED.lower() == "true"): 40 | if 'labels' in test_yml and bool(test_yml['labels']): 41 | name = test_yml["name"] 42 | attributes = [{'key': key, 'value': value} for key, value in test_yml['labels'].items()] 43 | report_portal_test_id = report_portal_service.start_test_item(name = name, item_type = 'step', attributes=attributes, start_time = timestamp()) 44 | logger.info(f"attributes with {report_portal_test_id} and {name} are {attributes}") 45 | else: 46 | report_portal_test_id = report_portal_service.start_test_item(name = test_yml["name"], item_type = 'step', start_time = timestamp()) 47 | logger.info("Labels are not set in the test") 48 | test_executor = TestExecutor(1, test_uuid, initial_variable_dictionary, test_yml, None ,test_result_object,report_portal_service,report_portal_test_id) 49 | else: 50 | test_executor= TestExecutor(1, test_uuid, initial_variable_dictionary, test_yml, None ,test_result_object) 51 | testExecutorObjects.append(test_executor) 52 | 53 | with ThreadPoolExecutor(max_workers=3) as pool_executor: 54 | for test_executor in testExecutorObjects: 55 | try: 56 | pool_executor.submit(execute_test, test_executor, email) 57 | if(TEST_RESULT_DB!= None and TEST_RESULT_DB.lower() == "postgres"): 58 | test_executor.test_result.Status="In_Progress" 59 | test_executor.test_result.save() 60 | logger.info("Task submitted") 61 | except Exception as e: 62 | logger.error("Exception occurred", e) 63 | pass 64 | 65 | if REPORT_PORTAL_ENABLED != None and REPORT_PORTAL_ENABLED.lower() == "true": 66 | report_portal_service.finish_launch(end_time=timestamp(), launch_uuid=launch_id) 67 | logger.info(f"Tests finished. Ending launch Daksha_test_{test_uuid} in Report Portal ") 68 | report_portal_service.terminate() 69 | 70 | logger.info("All threads complete, generating test report") 71 | generate_report(test_uuid) 72 | report_url = APACHE_URL + test_executor.test_uuid + '/report.html' 73 | send_report_email(test_executor.test_uuid, report_url, email) 74 | 75 | -------------------------------------------------------------------------------- /engine/utils/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | from github import Github 19 | import yaml 20 | import base64 21 | from daksha.settings import STORAGE_PATH, GIT_USER, GIT_PASS, REPO_ORG, REPO_USER 22 | from engine.logs import * 23 | 24 | 25 | def git_login(): 26 | if len(GIT_USER) == 0 or len(GIT_PASS) == 0: 27 | logger.info("Username OR password not given, mode is : public repository") 28 | github = Github() 29 | return get_org_instance(github, REPO_USER, REPO_ORG) 30 | else: 31 | github = Github(GIT_USER, GIT_PASS) 32 | return get_org_instance(github, REPO_USER, REPO_ORG) 33 | 34 | 35 | def get_file_content(repo_name, branch_name, file_path): 36 | org = git_login() 37 | logger.info("Fetching the content from %s of %s branch %s ", file_path, repo_name, branch_name) 38 | repo = org.get_repo(repo_name) 39 | file_content = repo.get_contents(file_path, branch_name) 40 | return file_content 41 | 42 | 43 | # Download the file from github 44 | def download_file_content(file_content, test_uuid): 45 | content = base64.b64decode(file_content.content) 46 | ymlcontent = yaml.full_load(content) 47 | logger.info(ymlcontent) 48 | file_path = f"{STORAGE_PATH}/{test_uuid}/" 49 | 50 | try: 51 | with open(file_path + file_content.name, "w") as file: 52 | yaml.dump(ymlcontent, file) 53 | file.close() 54 | logger.info(file_content.name) 55 | logger.info("File %s downloaded", file_content.path) 56 | except IOError as exc: 57 | logger.error('Error creating file : %s', exc) 58 | 59 | 60 | # read yaml file 61 | def read_yaml(repo, branch, file_path, test_uuid): 62 | try: 63 | file_content = get_file_content(repo, branch, file_path) 64 | download_file_content(file_content, test_uuid) 65 | file_name = f"{STORAGE_PATH}/{test_uuid}/{file_content.name}" 66 | with open(file_name, 'r') as stream: 67 | yaml_content = yaml.full_load(stream) 68 | logger.info("Find your text file at location %s" % file_name) 69 | return yaml_content 70 | except Exception: 71 | logger.error("File %s is not present in %s branch of %s" % (file_path, branch, repo)) 72 | return None 73 | 74 | 75 | def read_local_yaml(file_path): 76 | with open(file_path, 'r') as stream: 77 | yaml_content = yaml.full_load(stream) 78 | return yaml_content 79 | 80 | 81 | def get_org_instance(github, repo_user, repo_org): 82 | if len(repo_user) != 0 and len(repo_org) != 0: 83 | logger.error("Please provide either REPO_USER or REPO_ORG") 84 | raise Exception("Please provide either REPO_USER or REPO_ORG, terminating engine...") 85 | if len(repo_user) == 0 and len(repo_org) == 0: 86 | logger.error("Both REPO_USER or REPO_ORG are empty, please provide one") 87 | raise Exception("Both REPO_USER or REPO_ORG are empty, please provide one, terminating engine...") 88 | if len(repo_org) != 0: 89 | logger.info("REPO_ORG is present in config, accessing via : get_organization") 90 | org = github.get_organization(repo_org) 91 | else: 92 | logger.info("REPO_USER is present in config, accessing via : get_user") 93 | org = github.get_user(repo_user) 94 | return org 95 | 96 | 97 | def get_yml_files_in_folder_local(folder_path): 98 | files = [] 99 | for file in os.listdir(folder_path): 100 | if file.endswith(".yaml") or file.endswith(".yml"): 101 | file_path = os.path.join(folder_path, file) 102 | files.append(file_path) 103 | return files 104 | 105 | 106 | def get_yml_files_in_folder_git(repo_name: str, branch_name: str, folder_path: str): 107 | org = git_login() 108 | logger.info("Fetching yml files from %s of %s branch %s ", folder_path, repo_name, branch_name) 109 | repo = org.get_repo(repo_name) 110 | contents = repo.get_contents(folder_path, branch_name) 111 | files = [] 112 | for content in contents: 113 | if content.path.endswith(".yml"): 114 | files.append(content.path) 115 | return files 116 | -------------------------------------------------------------------------------- /engine/testreport_generator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | import json 18 | import os 19 | import shutil 20 | import string 21 | import random 22 | 23 | from .logs import logger 24 | 25 | from daksha.settings import STORAGE_PATH 26 | from .models import TestResult 27 | from datetime import datetime 28 | 29 | 30 | def generate_result(test_uuid, response, name, step, error_stack): 31 | """ 32 | Generates Test Result 33 | :param test_uuid: ID of the Test 34 | :type test_uuid: str 35 | :param response: Execution Status 36 | :type response: bool 37 | :param step : Last executed step of the Test 38 | :type step: dict 39 | :param error_stack: Error Stack if Test failed 40 | :type error_stack: str 41 | :param name: Name of the Test mentioned in YAML 42 | :type name: str 43 | """ 44 | try: 45 | logger.info('Creating test result') 46 | time_now = datetime.now() 47 | processed_time_now = time_now.__str__().replace(":", "_") 48 | result_file_path = f"{STORAGE_PATH}/{test_uuid}/result/{name}_{processed_time_now}" 49 | os.makedirs(os.path.dirname(result_file_path), exist_ok=True) 50 | if response: 51 | test_status = "Passed" 52 | step = "" 53 | error_stack = "" 54 | 55 | else: 56 | test_status = "Failed" 57 | error_stack = error_stack.replace('<', '<').replace('>', '>') 58 | test_result = TestResult(name, test_status, step.__str__(), error_stack) 59 | 60 | with open(result_file_path, 'w') as f: 61 | json.dump(test_result.__dict__, f) 62 | f.close() 63 | except Exception: 64 | logger.error("Error in test result generation:", exc_info=True) 65 | 66 | 67 | def generate_test_uuid(): 68 | """ 69 | Generates an unique 11 Digit Test ID 70 | :returns : 11 Digit unique Test ID 71 | :rtype: str 72 | """ 73 | res = ''.join(random.choices(string.ascii_uppercase + 74 | string.digits, k=11)) 75 | logger.info("Test UUID: " + res) 76 | return res 77 | 78 | 79 | def generate_report(test_uuid): 80 | """ 81 | Generates Test Report 82 | :param test_uuid: ID of the Test 83 | :type test_uuid: str 84 | """ 85 | try: 86 | logger.info('Creating test report') 87 | passed_count, failed_count = 0, 0 88 | test_result = [] 89 | result_folder_path = f"{STORAGE_PATH}/{test_uuid}/result" 90 | for file in os.listdir(result_folder_path): 91 | result_file_path = os.path.join(result_folder_path, file) 92 | with open(result_file_path) as f: 93 | for line in f: 94 | test_result.append(json.loads(line.strip())) 95 | if json.loads(line)["test_status"] == "Passed": 96 | passed_count += 1 97 | else: 98 | failed_count += 1 99 | test_result = json.dumps(test_result) 100 | replacement = {"test_uuid": test_uuid, "test_result": test_result, "passed_count": passed_count.__str__(), 101 | "failed_count": failed_count.__str__()} 102 | 103 | report_file_dir = f"{STORAGE_PATH}/{test_uuid}/" 104 | shutil.copy("templates/report.html", report_file_dir) 105 | shutil.copy("templates/report.js", report_file_dir) 106 | shutil.copy("templates/report.css", report_file_dir) 107 | with open(f"{report_file_dir}/report.html", "r+") as file: 108 | content = string.Template(file.read()).substitute(**replacement) 109 | file.seek(0) 110 | file.write(content) 111 | file.truncate() 112 | with open(f"{report_file_dir}/report.js", "r+") as file: 113 | content = string.Template(file.read()).substitute(**replacement) 114 | file.seek(0) 115 | file.write(content) 116 | file.truncate() 117 | logger.info("Test Report generated") 118 | except Exception: 119 | logger.error("Error in test report generation:", exc_info=True) 120 | 121 | -------------------------------------------------------------------------------- /templates/report.js: -------------------------------------------------------------------------------- 1 | function generateTable() { 2 | try { 3 | var testdetails = document.createElement("div"); 4 | testdetails.classList.add("chart_div"); 5 | testdetails.setAttribute("id", "piechart"); 6 | var dvTable = document.getElementById("testdetails"); 7 | dvTable.parentNode.replaceChild(testdetails, dvTable); 8 | 9 | drawChart(); 10 | 11 | var table = document.createElement("Nav"); 12 | table.classList.add("sidenav"); 13 | var testdata = ${test_result}; 14 | 15 | 16 | //Get the count of columns. 17 | 18 | var count = testdata.length; 19 | var element = document.createElement("c"); 20 | element.innerHTML = "Test Details Overview"; 21 | element.onclick = function () { 22 | loadpiechart(testdata); 23 | }; 24 | table.appendChild(element); 25 | for (var i = 0; i < count; i++) { 26 | if (testdata[i].test_status == "Passed") { 27 | var element = document.createElement("a"); 28 | 29 | element.innerHTML = testdata[i].test_name; 30 | element.onclick = function () { 31 | loadpiechart(testdata); 32 | }; 33 | 34 | 35 | } else { 36 | var element = document.createElement("b"); 37 | element.innerHTML = testdata[i].test_name; 38 | element.onclick = function () { 39 | loadTestDetails(testdata); 40 | }; 41 | 42 | 43 | } 44 | 45 | 46 | table.appendChild(element); 47 | } 48 | 49 | var dvTable = document.getElementById("report"); 50 | dvTable.innerHTML = ""; 51 | dvTable.appendChild(table); 52 | } 53 | catch (e) { 54 | 55 | } 56 | } 57 | 58 | function loadTestDetails(testdata) { 59 | try { 60 | var testdetails = document.createElement("testdetails"); 61 | testdetails.setAttribute("id", "testdetails"); 62 | var count = testdata.length; 63 | 64 | for (var i = 0; i < count; i++) { 65 | 66 | if (testdata[i].test_status != "Passed") { 67 | 68 | var element = document.createElement("div"); 69 | element.classList.add("failcard"); 70 | element.innerHTML = testdata[i].test_name + "----Test Failed"; 71 | testdetails.appendChild(element); 72 | element = document.createElement("div"); 73 | testdetails.appendChild(element); 74 | element = document.createElement("div"); 75 | element.innerHTML = "Failed Step: " + testdata[i].failed_step; 76 | element.classList.add("detail"); 77 | testdetails.appendChild(element); 78 | 79 | var element = document.createElement("div"); 80 | element.classList.add("detail"); 81 | element.innerHTML = testdata[i].failure_cause; 82 | 83 | testdetails.appendChild(element); 84 | for (j = 0; j < 2; j++) { 85 | element = document.createElement("br"); 86 | testdetails.appendChild(element); 87 | } 88 | 89 | 90 | } 91 | 92 | 93 | } 94 | var dvTable = document.getElementById("piechart"); 95 | dvTable.parentNode.replaceChild(testdetails, dvTable); 96 | } 97 | catch (e) 98 | { 99 | 100 | } 101 | 102 | } 103 | function loadpiechart(data) 104 | { 105 | try { 106 | var testdetails = document.createElement("div"); 107 | 108 | testdetails.setAttribute("id", "piechart"); 109 | testdetails.classList.add("chart_div"); 110 | var dvTable = document.getElementById("testdetails"); 111 | dvTable.parentNode.replaceChild(testdetails, dvTable); 112 | 113 | drawChart(); 114 | } catch (e) 115 | { 116 | 117 | } 118 | } 119 | google.charts.load('current', {'packages':['corechart']}); 120 | google.charts.setOnLoadCallback(drawChart); 121 | function drawChart() { 122 | try { 123 | 124 | var data = google.visualization.arrayToDataTable([ 125 | ['Test Result ', 'Number of tests'], 126 | 127 | ['Failed', ${failed_count}], ['Passed', ${passed_count}] 128 | ]); 129 | 130 | // add a title and set the width and height of the chart 131 | var options = { 132 | 'title': 'Test Result', 'width': "100%", 'height': "100%", pieHole: 0.4, colors: ["#CC5500", "#32CD32"] 133 | 134 | }; 135 | 136 | // Display the chart inside the
element with id="piechart" 137 | var chart = new google.visualization.PieChart(document.getElementById('piechart')); 138 | chart.draw(data, options); 139 | }catch (e) { 140 | 141 | } 142 | 143 | 144 | } -------------------------------------------------------------------------------- /daksha_know-how/CreateTest.md: -------------------------------------------------------------------------------- 1 | # Writing Your First Test Case with Daksha 2 | 3 | You need to write test case in yml file and it should be in the following format - 4 | ```config: 5 | name: 6 | task: 7 | ``` 8 | A complete test case example is given below along with the description of each field and functionality that we are supporting: 9 | 10 | ``` 11 | config: 12 | env: remote 13 | browser: chrome 14 | #driverAddress: /Users/IdeaProjects/qa-automation/ui_automation_engine/drivers/chromedriver 15 | driverAddress: http://192.168.21.185:4444/wd/hub 16 | name:TestQA 17 | labels: 18 | team: your_team 19 | alert_type : gchat 20 | task: 21 | - launch_browser 22 | - open_url: 23 | url: https://app.mykaarma.com 24 | - fill_data: 25 | xpath: //*[@id='firstPhaseInputs']//input[@placeholder='Username'] 26 | value: '{{ username }}' 27 | - click_button: 28 | xpath: //*[@id='bSignIn'] 29 | - fill_data: 30 | xpath: //*[@id='password'] 31 | value: '{{ password }}' 32 | - click_button: 33 | xpath: //*[@id='bchromedriverSignIn'] 34 | - wait_for: 35 | mode: visibility 36 | xpath: //div[@class='headertoprightDealerName'] 37 | - validate_ui_element: 38 | mode: equals 39 | xpath: //div[@class='headertoprightDealerName'] 40 | value: myKaarma 41 | - wait_for: 42 | mode: hardwait 43 | value: 20 44 | - click_button: 45 | xpath: //*[text()='Sign Out'] 46 | - wait_for: 47 | mode: invisibility 48 | xpath: //div[@class='headertoprightDealerName'] 49 | 50 | - quit_browser 51 | ``` 52 | 53 | 54 | * **config**-This should contain the browser configurations. It has three fields namely env,browser,driverAddress. 55 | * **env**-It may have two values- remote or local. For witnessing your tests being executed in your device,you can set this to local and then give the browser details in the next 2 fileds. Similarly if you want your test to be executed at a remote browser you can set this to remote.Users can use a VNC client to see the tests being executed in remote browser. 56 | * **browser**-Currently we are supporting chrome only, so the value should be chrome. 57 | * **driverAddress**-You can either give location of your local chrome browser or the url at which your remote chrome browser is hosted. 58 | * **name**- Give a name to your testcase.This will be dispayed in your test report. 59 | * **labels**- This is an optional field. The user can provide labels regarding the test. These will be used in setting up attributes in Report Portal which will help in setting up dashboard. 60 | * **alert_type**- Include this in your yaml if you want to send the test failure alerts to any communication channel like gchat/slack,if not included it will not send any alerts.Currently it supports values - gchat,slack.You will have to set *ALERT_URL* env variable in case you have set *alert_type* in your yaml. 61 | * **task**- This will contain the steps of your test.For writing this section please take a look at the list of functionalities that we are providing in the below list.You can perform simple to complex test cases with a combination of the below functionalities. 62 | * ***launch_browser***: This will launch the browser in the environment and location provided in the config field.You should always include this step in the start of your test case steps. 63 | * ***open_url***: Provide the url of the site that you want to perform tests on. 64 | * ***fill_data***: This has two sub-fields,namely *locator* and *value* . **Locator** can be *xpath, id, css, name, tagname, classname, linktext and partiallinktext*. In **locator** you should provide the type of locator you are providing followed by the webelement where you want to fill the data and in the *value* field provide the data that you want to enter. 65 | * ***'{{ variablename }}'***: This is placeholder variable, you should provide their values through ApiRequest. 66 | * ***click_button***: You need to provide the locator of the webelement that you want to click. 67 | * ***validate_ui_element***: This has 3 field: *mode, locator, value*.In *mode* you can select 3 types of validation method namely **equals,contains and not contains**.In *locator* give the locator of your webelement and in *value* give the text with which you want to validate. 68 | * **refresh_page**: This can be added to refresh the webpage. 69 | * **open_new_tab**: This will open a new tab in your browser. 70 | * **switch_to_tab**: Here the title of the tab to which you want to move to needs to be provided as a subfield. 71 | * **navigate_back**: This can be used to go back to previous page. 72 | * **switch_iframe**: Here you need to provide the **locator** as a subfield and it will switch to that iframe. 73 | * **switch_to_default_iframe**: This will take you to the default frame. 74 | * **execute_js**: Execute any js command in the browser. 75 | * **scroll_to**: You need to provide the locator of the webelement that you want to scroll down/up to.**Locator** can be *xpath, id, css, name, tagname, classname, linktext and partiallinktext* 76 | * **wait-for**: This has 2 fields- mode, value/locator.This has 3 mode : *visibility,invisibility,hardwait*.For *visibility/invisibility* please provide the locator of the webelement that you want to wait for. In case of mode *hardwait*, please provide the value as number of seconds you want to wait for. 77 | * **quit_browser**: You are recommended to add this step at the end of your test case steps to quit the remote or local browser. 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/Components/dakshaRecorderMainPage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | */ 16 | /*global chrome*/ 17 | import React from "react"; 18 | import "../ComponentCss/dakshaRecorderMainPage.css"; 19 | import PlayButton from '../Icons/PlayButton.png'; 20 | import PauseButton from '../Icons/PauseButton.png'; 21 | import StopButton from '../Icons/StopButton.png'; 22 | import RightArrow from '../Icons/RightArrow.png'; 23 | import removeBadgeForRecording from "./removeBadgeForRecording"; 24 | import setBadgeForRecording from "./setBadgeForRecording"; 25 | import GlobalVariables from "./globalConfigs"; 26 | let dakshaRecorderCustomHardwaitPage = GlobalVariables.dakshaRecorderCustomHardwaitPage; 27 | let dakshaRecorderEndPage = GlobalVariables.dakshaRecorderEndPage; 28 | let play = 2 ; 29 | let pause = 1 ; 30 | function PlayPause(props) { 31 | 32 | if (props.image === pause) 33 | return ( 34 | { 35 | props.changeImage(2); 36 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 37 | let obj = { 38 | "type": "pause" 39 | } 40 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 41 | removeBadgeForRecording(); 42 | }) 43 | } 44 | } /> 45 | ) 46 | else if (props.image === play) { 47 | return ( 48 | { 49 | props.changeImage(1); 50 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 51 | let obj = { 52 | "type": "resume" 53 | } 54 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 55 | setBadgeForRecording(); 56 | }) 57 | }} /> 58 | ) 59 | } 60 | } 61 | function DakshaRecorderMainPage(props) { 62 | return ( 63 | <> 64 |
65 |
66 | Daksha Recorder 67 |
68 |
69 | 70 | { 71 | props.setState(dakshaRecorderEndPage); 72 | removeBadgeForRecording(); 73 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 74 | let obj = { 75 | "type": "stop" 76 | } 77 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 78 | }) 79 | }} /> 80 |
81 |
82 |
83 |
{ 84 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 85 | let obj = { 86 | "type": "tenSecondsWait" 87 | } 88 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 89 | }) 90 | }}> 91 | HARD WAIT (10 SEC) 92 |
93 |
props.setState(dakshaRecorderCustomHardwaitPage)}> 94 | 95 |
96 |
97 |
{ 98 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 99 | let obj = { 100 | "type": "viewYaml" 101 | } 102 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 103 | }) 104 | }}> 105 | VIEW YAML 106 |
107 |
{ 108 | chrome.tabs.query({ active: true, lastFocusedWindow: true }, tabs => { 109 | let obj = { 110 | "type": "undoLastStep" 111 | } 112 | chrome.tabs.sendMessage(tabs[0].id, obj, () => { return true; }); 113 | }) 114 | }}> 115 | UNDO LAST STEP 116 |
117 |
118 |
119 | 120 | ) 121 | } 122 | 123 | export default DakshaRecorderMainPage; -------------------------------------------------------------------------------- /engine/executor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | from jinja2 import UndefinedError 18 | 19 | from .alert_sender import * 20 | from .method_mapper import * 21 | from .models import TestExecutor 22 | from .selenium_helper import * 23 | from .testreport_generator import * 24 | from .email_generator import * 25 | from daksha import settings 26 | from engine import test_result_utils 27 | 28 | import jinja2 29 | import ast 30 | 31 | 32 | def __cleanup(web_driver: WebDriver): 33 | try: 34 | web_driver.quit() 35 | except Exception: 36 | pass 37 | 38 | 39 | def execute_test(test_executor: TestExecutor, email): 40 | """ 41 | Calls method to execute the steps mentioned in YAML and calls methods for report generation and sending test report email 42 | :param test_yml: The test yaml containing test config, name and task 43 | :type test_yml: dict 44 | :param test_executor: The TestExecutor object to give context for execution 45 | :type test_executor: TestExecutor 46 | :param email: The email where the report will be sent 47 | :type email: str 48 | """ 49 | try: 50 | if(REPORT_PORTAL_ENABLED != None and REPORT_PORTAL_ENABLED.lower() == "true"): 51 | report_portal_logging_handler.set_item_id(test_executor.report_portal_test_id) 52 | report_portal_logging_handler.set_service(test_executor.report_portal_service) 53 | execution_result, error_stack = True, None 54 | step = {} 55 | test_yml = test_executor.test_yml 56 | config = test_yml["config"] 57 | task = test_yml["task"] 58 | name = test_yml["name"] # TODO: Alert user if no/null config/task/name is provided 59 | alert_type = None 60 | if "alert_type" in test_yml: 61 | alert_type = test_yml['alert_type'] 62 | logger.info("Users has opted for alerts via " + alert_type) 63 | else: 64 | logger.info("User has not opted for alerts") 65 | web_driver = browser_config(config) 66 | test_executor.web_driver = web_driver 67 | 68 | if(REPORT_PORTAL_ENABLED != None and REPORT_PORTAL_ENABLED.lower() == "true"): 69 | report_portal_service=test_executor.report_portal_service 70 | report_portal_test_id=test_executor.report_portal_test_id 71 | 72 | for step in task: 73 | execution_result, error_stack = execute_step(test_executor, step) 74 | if execution_result is False: 75 | break 76 | logger.info("Test " + name + " finished, generating result ") 77 | 78 | if(REPORT_PORTAL_ENABLED != None and REPORT_PORTAL_ENABLED.lower() == "true"): 79 | if execution_result: 80 | report_portal_service.finish_test_item(status="PASSED",item_id=report_portal_test_id, end_time=timestamp()) 81 | else: 82 | report_portal_service.finish_test_item(status="FAILED",item_id=report_portal_test_id,end_time=timestamp()) 83 | 84 | generate_result(test_executor.test_uuid, execution_result, name, step, error_stack ) 85 | 86 | if settings.TEST_RESULT_DB != None and settings.TEST_RESULT_DB.lower() == "postgres": 87 | test_result_utils.save_result_in_db(test_executor,execution_result,step,error_stack) 88 | 89 | if execution_result: 90 | logger.info("Test " + name + " successful") 91 | else: 92 | logger.info("Test " + name + " failed for test ID: " + test_executor.test_uuid) 93 | send_alert(test_executor.test_uuid, name, str(step), error_stack, alert_type) 94 | __cleanup(web_driver) 95 | 96 | if(REPORT_PORTAL_ENABLED != None and REPORT_PORTAL_ENABLED.lower() == "true"): 97 | report_portal_logging_handler.clear_item_id() 98 | report_portal_logging_handler.clear_service() 99 | 100 | except Exception: 101 | logger.error("Error encountered in executor: ", exc_info=True) 102 | if(REPORT_PORTAL_ENABLED != None and REPORT_PORTAL_ENABLED.lower() == "true"): 103 | report_portal_logging_handler.clear_item_id() 104 | report_portal_logging_handler.clear_service() 105 | 106 | 107 | def execute_step(test_executor: TestExecutor, step): 108 | """ 109 | Executes steps mentioned in YAML 110 | :param test_executor: The TestExecutor object to give context for execution 111 | :type test_executor: TestExecutor 112 | :param step: Test steps mentioned in YAML 113 | :type step: dict 114 | :raises : KeyError 115 | :returns: Status of Execution and error stack 116 | :rtype: tuple 117 | """ 118 | try: 119 | logger.info("Executing:\t" + str(type(step)) + '\t' + str(step)) 120 | # https://stackoverflow.com/a/40219576 121 | # https://note.nkmk.me/en/python-argument-expand/ 122 | execution_success = False 123 | error_stack = None 124 | if isinstance(step, str): 125 | logger.info("Gonna process the method directly") 126 | execution_success, error_stack = method_map[step](test_executor=test_executor) 127 | elif isinstance(step, dict): 128 | logger.info("Gonna render the variables") 129 | # raise error if a variable present in yml file but not present in variable dictionary 130 | template = jinja2.Template(str(step), undefined=jinja2.StrictUndefined) 131 | # rendered the variables from the variable dictionary 132 | step_render = template.render(test_executor.variable_dictionary) 133 | # converting the final string with rendered variables to dictionary 'step' 134 | step = ast.literal_eval(step_render) 135 | logger.info("Gonna call this method with args") 136 | for k, v in step.items(): 137 | logger.info(str(type(v)) + "\t. " + str(v)) 138 | execution_success, error_stack = method_map[k](test_executor=test_executor, **v) 139 | break 140 | logger.info("fin") 141 | if execution_success is False: 142 | return False, error_stack 143 | else: 144 | return True, error_stack 145 | except UndefinedError as e: 146 | logger.error("Error in rendering variable: ", exc_info=True) 147 | return False, "Error in rendering variable: " + str(e) 148 | except Exception: 149 | logger.error("Error encountered: ", exc_info=True) 150 | return False, traceback.format_exc() 151 | -------------------------------------------------------------------------------- /engine/api_response.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty tof 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | import requests 19 | import jmespath 20 | from requests.auth import HTTPBasicAuth, HTTPDigestAuth, HTTPProxyAuth 21 | 22 | from .logs import logger 23 | from json import JSONDecodeError 24 | from django.http import HttpResponse 25 | from rest_framework import status 26 | 27 | from .models import TestExecutor 28 | 29 | 30 | def get_arguments_info(param: str, **kwargs): 31 | """ 32 | get arguments info to that mentioned in YAML 33 | :param param: Parameter 34 | :type param: object 35 | :param kwargs: WebElement Description Fetched From YAML 36 | :type kwargs: dict 37 | :returns; Parameter in the YAML or None 38 | 39 | """ 40 | if param in kwargs.keys(): 41 | return kwargs[param] 42 | else: 43 | return None 44 | 45 | 46 | def get_authentication_(**kwargs): 47 | """ 48 | get authentication info to that mentioned in YAML 49 | :param kwargs: WebElement Description Fetched From YAML 50 | :type kwargs: dict 51 | :returns; Authentication Mode with username and password or None 52 | 53 | """ 54 | auth = get_arguments_info('auth', **kwargs) 55 | if auth is None: 56 | return None 57 | if auth["type"] == "Basic": 58 | return HTTPBasicAuth(auth["username"], auth["password"]) 59 | elif auth["type"] == "Proxy": 60 | return HTTPProxyAuth(auth["username"], auth["password"]) 61 | elif auth["type"] == "Digest": 62 | return HTTPDigestAuth(auth["username"], auth["password"]) 63 | 64 | 65 | def make_http_request(test_executor: TestExecutor, **kwargs): 66 | """ 67 | makes the http request to that mentioned in YAML 68 | :param test_executor: The TestExecutor object to give context for execution 69 | :type test_executor: TestExecutor 70 | :param kwargs: WebElement Description Fetched From YAML 71 | :type kwargs: dict 72 | :returns; Response of the Request made 73 | 74 | """ 75 | if kwargs['request'] == 'GET': 76 | r = requests.get(kwargs['url'], json=get_arguments_info('payload', **kwargs), 77 | headers=get_arguments_info('headers', **kwargs), 78 | auth=get_authentication_(**kwargs), cookies=get_arguments_info('cookies', **kwargs), 79 | proxies=get_arguments_info('proxy', **kwargs), timeout=get_arguments_info('timeout', **kwargs)) 80 | return process_response(test_executor, r, **kwargs) 81 | elif kwargs['request'] == 'POST': 82 | r = requests.post(kwargs['url'], data=get_arguments_info('data', **kwargs), 83 | json=get_arguments_info('data', **kwargs), 84 | headers=get_arguments_info('headers', **kwargs), auth=get_authentication_(**kwargs), 85 | cookies=get_arguments_info('cookies', **kwargs), 86 | proxies=get_arguments_info('proxy', **kwargs), 87 | timeout=get_arguments_info('timeout', **kwargs)) 88 | return process_response(test_executor, r, **kwargs) 89 | elif kwargs['request'] == 'PUT': 90 | r = requests.put(kwargs['url'], data=get_arguments_info('payload', **kwargs), 91 | json=get_arguments_info('payload', **kwargs), 92 | headers=get_arguments_info('headers', **kwargs), auth=get_authentication_(**kwargs), 93 | cookies=get_arguments_info('cookies', **kwargs), proxies=get_arguments_info('proxy', **kwargs), 94 | timeout=get_arguments_info('timeout', **kwargs)) 95 | return process_response(test_executor, r, **kwargs) 96 | elif kwargs['request'] == 'DELETE': 97 | r = requests.delete(kwargs['url'], json=get_arguments_info('payload', **kwargs), 98 | headers=get_arguments_info('headers', **kwargs), 99 | auth=get_authentication_(**kwargs), cookies=get_arguments_info('cookies', **kwargs) 100 | , proxies=get_arguments_info('proxy', **kwargs), 101 | timeout=get_arguments_info('timeout', **kwargs)) 102 | return process_response(test_executor, r, **kwargs) 103 | else: 104 | logger.error("Reuest method not supported :(") 105 | return False, HttpResponse("Reuest method not supported :(", 106 | status=status.HTTP_400_BAD_REQUEST) 107 | 108 | 109 | def process_response(test_executor: TestExecutor, r, **kwargs): 110 | """ 111 | processes the http request to that mentioned in YAML 112 | :param test_executor: The TestExecutor object to give context for execution 113 | :type test_executor: TestExecutor 114 | :param r: Request made 115 | :type r: request 116 | :param kwargs: WebElement Description Fetched From YAML 117 | :type kwargs: dict 118 | :returns; Status of Execution and error stack 119 | 120 | """ 121 | response_dict = get_arguments_info('response', **kwargs) 122 | if response_dict is None: 123 | r.raise_for_status() 124 | return True, None 125 | if 'status' not in response_dict.keys(): 126 | # use raiser_for_status() if the status code is not present 127 | # 2xx/3xx - pass; 4xx/5xx - raises error 128 | r.raise_for_status() 129 | else: 130 | # check if status_code matches the status provided 131 | if r.status_code != response_dict['status']: 132 | logger.info(str(r.status_code) + " Status Not Matched :(") 133 | logger.info(r.text) 134 | return False, "Status Not Matched : " + r.text 135 | logger.info(str(r.status_code) + " OK! Proceeding further") 136 | return save_response(test_executor, r, response_dict) 137 | 138 | 139 | def save_response(test_executor: TestExecutor, r, response_dict): 140 | """ 141 | processes the http request to that mentioned in YAML 142 | :param test_executor: The TestExecutor object to give context for execution 143 | :type test_executor: TestExecutor 144 | :type r: request 145 | :param response_dict: response dict fetched from YAML 146 | :type response_dict: dict 147 | :returns; Status of Execution and error stack 148 | 149 | """ 150 | try: 151 | logger.info(r.json()) 152 | if 'save' in response_dict: 153 | for entry in response_dict['save']: 154 | b = entry['save in'] 155 | # if key is not present in dict, whole json will be saved to the 'save in' parameter 156 | if 'key' in entry: 157 | test_executor.variable_dictionary[b] = jmespath.search(entry['key'], r.json()) 158 | else: 159 | test_executor.variable_dictionary[b] = r.text 160 | logger.info(str(test_executor.variable_dictionary) + " new key, value pairs added") 161 | except JSONDecodeError: 162 | logger.info(r.text) 163 | # save the text response in the variable_dict 164 | if 'save' in response_dict: 165 | for entry in response_dict['save']: 166 | b = entry['save in'] 167 | test_executor.variable_dictionary[b] = r.text 168 | logger.info(str(test_executor.variable_dictionary) + " new (key,value) pairs added;" 169 | " value contains the whole text received") 170 | return True, None 171 | -------------------------------------------------------------------------------- /daksha_know-how/RequestAPI.md: -------------------------------------------------------------------------------- 1 | # Making API calls with Daksha 2 | 3 | You need to write test case in yml file and it should be in the following format - 4 | ```config: 5 | name: 6 | task: 7 | ``` 8 | A complete test case example is given below along with the description of each field and functionality that we are supporting: 9 | 10 | ``` 11 | config: 12 | env: remote 13 | browser: chrome 14 | #driverAddress: /Users/IdeaProjects/qa-automation/ui_automation_engine/drivers/chromedriver 15 | driverAddress: http://192.168.21.185:4444/wd/hub 16 | name:APISupportTest 17 | task: 18 | - make_HTTP_request: 19 | request: 'POST' 20 | url : 'https://fakerestapi.azurewebsites.net/api/v1/Activities' 21 | headers : 22 | accept: text/plain; v=1.0 23 | Content-Type: application/json; v=1.0 24 | data: ' 25 | { 26 | "id": 0, 27 | "title": "string", 28 | "dueDate": "2021-06-23T18:03:18.689Z", 29 | "completed": true 30 | }' 31 | response: 32 | 'status' : 200 #first check the response and then we will move ahead 33 | save: #user need to pass this save key and the values must be passed as lists 34 | - 'key' : "title" 35 | 'save in' : title 36 | - make_HTTP_request: 37 | request : 'GET' 38 | url : 'https://httpbin.org/basic-auth/{{username}}/{{passwd}}' 39 | headers: 40 | accept : 'application/json' 41 | auth: 42 | type : "Basic" #can be Basic, Digest and Proxy 43 | username : "{{ username}}" 44 | password : "{{ passwd }}" 45 | Cookies: 46 | custom_cookie : 47 | 'cookie_name': 'cookie_value' 48 | timeout: 12 #it can be integer or tuple 49 | response: 50 | 'status' : 200 #after making the http request, first check the response and then we will move ahead 51 | save: #user need to pass this save key and the values must be passed as lists 52 | - 'save in' : response_get_body 53 | - make_HTTP_request: 54 | request: 'PUT' 55 | url : 'https://fakerestapi.azurewebsites.net/api/v1/Activities/{{id}}' 56 | headers : 57 | accept: text/plain; v=1.0 58 | Content-Type: application/json; v=1.0 59 | payload: '{ 60 | "id": 1, 61 | "title": "string", 62 | "dueDate": "2021-06-14T03:56:36.635Z", 63 | "completed": true 64 | }' 65 | # response: 66 | # 'status' : 200 #no status code provided 67 | - make_HTTP_request: 68 | request: 'POST' 69 | url : 'https://httpbin.org/post' 70 | headers : 71 | 'accept' : 'application/json' 72 | data: > 73 | { 74 | "form": { 75 | "comments": "fast", 76 | "custemail": "rg@gmail.com", 77 | "custname": "Rg", 78 | "custtel": "783849", 79 | "delivery": "18:30", 80 | "size": "medium", 81 | "topping": [ 82 | "cheese", 83 | "onion", 84 | "mushroom" 85 | ] 86 | } 87 | } 88 | response: 89 | 'status' : 200 #after making the http request, first check the response and then we will move ahead 90 | - make_HTTP_request: 91 | request: 'DELETE' 92 | url : 'https://httpbin.org/delete' 93 | headers : 94 | 'accept' : 'application/json' 95 | response: 96 | 'status' : 201 #status mismatch 97 | save: #user need to pass this save key and the values must be passed as lists 98 | - 'save in' : response_delete_body 99 | ``` 100 | 101 | * **config**-This should contain the browser configurations. It has three fields namely env,browser,driverAddress. 102 | * **env**-It may have two values- remote or local. For witnessing your tests being executed in your device,you can set this to local and then give the browser details in the next 2 fileds. Similarly if you want your test to be executed at a remote browser you can set this to remote.Users can use a VNC client to see the tests being executed in remote browser. 103 | * **browser**-Currently we are supporting chrome only, so the value should be chrome. 104 | * **driverAddress**-You can either give location of your local chrome browser or the url at which your remote chrome browser is hosted. 105 | * **name**- Give a name to your testcase.This will be dispayed in your test report. 106 | * **task**- This will contain the steps of your test.For writing this section please take a look at the list of functionalities that we are providing in the below list.You can perform simple to complex test cases with a combination of the below functionalities.Only single task parameter is supported at a time. 107 | * **make_HTTP_request**- This will make the corresponding *HTTP* request to perform the CRUD operation. 108 | * **request**- User needs to Specify the type of request [GET,POST,PUT,DELETE] that they want to request. 109 | * **url**- Parameter of the request module to provide the url of the site that you want to request. 110 | * **headers**- Parameter of the request module to provide the headers. 111 | * The user can specify the ***accept*** key, to inform the server the type of content understandable by the user. If the ***accept*** header is not present in the request, then the server assumes that the client accepts all types of media. 112 | * The user can provide the ***Content-Type*** of the content that the will be returned from the server. *Optional key* 113 | * The user can provide ***Authorization*** with the *Basic*, *Bearer* and other authorizations and the *Tokens*. These *Tokens* can also be parameterized. *Optional key* 114 | * Many other details like *Cookies*, *Caches* etc can be provided in the **headers**. 115 | * Providing the details in **''** is optional. 116 | * **payload**- Parameter of the *GET* request. The user can provide the data using this key. 117 | * **data**- Parameter of the *POST*, *PUT* and *DELETE* 118 | * **json** - User can provide the *json* data for all the supported requests. 119 | User can either use *data*/*payload* or *json* while making the request. 120 | * While passing data through **payload**, **data** or **json**, user can either use *''* or *>* to provide the json data. 121 | * While using *''* user cannot use *''* inside the content provided, while he/she shouldnot worry about the indentation. 122 | * While using *>* user can provide the data containing *''* but he/she need to maintain the proper indentation of the data provided. 123 | * **auth**- User need to specify the ***type*** of authentication [Basic,Digest,Proxy], ***username*** and ***password***.*Optional Parameter*. 124 | * *Basic* will use *HTTPBasicAuth* auth token of the request module. 125 | * *Digest* will use *HTTPDigestAuth* auth token of the request module. 126 | * *Proxy* will use *HTTPProxyAuth* auth token of the request module. 127 | * User should use variable rendering functionality to pass username and password *recommended* 128 | * **Cookies**- User can send the cookies while making the request. User can provide the **custom-cookies**.*Optional Parameter* 129 | * **timeout**- Response will awaited till this time and *timeout exception* will be raised when exceeded.It can be either integer or tuple. *Optional Parameter* 130 | * **response**- This section will save the response received from the api call. 131 | * User can specify the **status** that he wants to receive [2xx,3xx,4xx,5xx]. If the *status* doesn't match it will return *Status_mismatch* error. *Optional parameter* 132 | * If the user does not provide the **status**, it will work for 2xx,3xx and will throw error for 4xx and above *status_code*. 133 | * The main function of this key is to **save** the response recevied, and use it further. 134 | * **save**- The user needs to provide the key **save_in**, the vairable where the response must be saved. This key can be used further. *Optional parameter* 135 | * The user can fetch the response data from the *nested json*. This must be provided in **key**. 136 | * If the response is nested dictionary then user can follow *parent_dict.child_dict.key*. 137 | * If the response has list in *parent_dict*, and the user want to access the ith dictionary key-value pair *parent_dict[i].key* 138 | * For further assistance, please read the **Jmespath** official documentation. 139 | * *Otional parameter* -------------------------------------------------------------------------------- /daksha/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | """ 18 | Django settings for daksha project. 19 | 20 | Generated by 'django-admin startproject' using Django 3.0.9. 21 | 22 | For more information on this file, see 23 | https://docs.djangoproject.com/en/3.0/topics/settings/ 24 | 25 | For the full list of settings and their values, see 26 | https://docs.djangoproject.com/en/3.0/ref/settings/ 27 | """ 28 | 29 | import os 30 | import yaml 31 | from daksha.rp_client_ext import RPClient 32 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 33 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 34 | 35 | 36 | # Quick-start development settings - unsuitable for production 37 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 38 | 39 | # SECURITY WARNING: keep the secret key used in production secret! 40 | SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'gj%7t91&c$*q&+fkx-wuug^i$0b@@lk26vd4txdq=)%_319hyy') 41 | 42 | # SECURITY WARNING: don't run with debug turned on in production! 43 | DEBUG = False 44 | 45 | ALLOWED_HOSTS = [ 46 | '127.0.0.1' 47 | ] 48 | CUSTOM_ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(sep=',') 49 | ALLOWED_HOSTS.extend(CUSTOM_ALLOWED_HOSTS) 50 | # Application definition 51 | 52 | INSTALLED_APPS = [ 53 | 'django.contrib.admin', 54 | 'django.contrib.auth', 55 | 'django.contrib.contenttypes', 56 | 'django.contrib.sessions', 57 | 'django.contrib.messages', 58 | 'django.contrib.staticfiles', 59 | 'engine', 60 | 'django_crontab' 61 | ] 62 | 63 | MIDDLEWARE = [ 64 | 'django.middleware.security.SecurityMiddleware', 65 | 'django.contrib.sessions.middleware.SessionMiddleware', 66 | 'django.middleware.common.CommonMiddleware', 67 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 68 | 'django.contrib.messages.middleware.MessageMiddleware', 69 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 70 | ] 71 | 72 | ROOT_URLCONF = 'daksha.urls' 73 | 74 | TEMPLATES = [ 75 | { 76 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 77 | 'DIRS': [], 78 | 'APP_DIRS': True, 79 | 'OPTIONS': { 80 | 'context_processors': [ 81 | 'django.template.context_processors.debug', 82 | 'django.template.context_processors.request', 83 | 'django.contrib.auth.context_processors.auth', 84 | 'django.contrib.messages.context_processors.messages', 85 | ], 86 | }, 87 | }, 88 | ] 89 | 90 | WSGI_APPLICATION = 'daksha.wsgi.application' 91 | 92 | 93 | # Database 94 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 95 | 96 | #If the user wants to use the database then he must set an Environment Variable named Test_Result_DB as "postgres" 97 | #if the value of this environment variable is not set then the test results will not be saved in the database 98 | TEST_RESULT_DB=os.environ.get('TEST_RESULT_DB',None) 99 | 100 | if (TEST_RESULT_DB !=None and TEST_RESULT_DB.lower() == "postgres"): 101 | DATABASES = { 102 | 'default': { 103 | 'ENGINE': 'django.db.backends.postgresql', 104 | 'NAME': os.environ.get('PG_DB','postgres'), 105 | 'USER':os.environ.get('PG_USER','postgres'), 106 | 'PASSWORD':os.environ.get('PG_PASSWORD','postgres'), 107 | 'HOST':os.environ.get('PG_HOST','localhost'), 108 | 'PORT':os.environ.get('PG_PORT',5432) 109 | } 110 | } 111 | 112 | #Starting with 3.2 new projects are generated with DEFAULT_AUTO_FIELD set to BigAutoField. 113 | #To avoid unwanted migrations in the future, either explicitly set DEFAULT_AUTO_FIELD to AutoField: 114 | DEFAULT_AUTO_FIELD='django.db.models.AutoField' 115 | 116 | # Password validation 117 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 118 | 119 | AUTH_PASSWORD_VALIDATORS = [ 120 | { 121 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 122 | }, 123 | { 124 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 125 | }, 126 | { 127 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 128 | }, 129 | { 130 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 131 | }, 132 | ] 133 | 134 | 135 | # Internationalization 136 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 137 | 138 | LANGUAGE_CODE = 'en-us' 139 | 140 | TIME_ZONE = 'UTC' 141 | 142 | USE_I18N = True 143 | 144 | USE_L10N = True 145 | 146 | USE_TZ = True 147 | 148 | # Static files (CSS, JavaScript, Images) 149 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 150 | STATIC_URL = '/static/' 151 | 152 | #Endpoint which is being hit regulary when cron jobs are executed 153 | daksha_endpoint="http://127.0.0.1:8000/daksha/runner" 154 | 155 | # Set Email to receive test reports 156 | EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '') 157 | POSTMARK_TOKEN = os.environ.get('POSTMARK_TOKEN', '') 158 | 159 | # Logs/reports configurations 160 | LOG_FILE = os.environ.get('LOG_FILE', 'logs/uiengine.log') 161 | STORAGE_PATH = os.environ.get('STORAGE_PATH', 'reports') 162 | APACHE_URL = os.environ.get('APACHE_URL', '') 163 | 164 | #Alert configurations 165 | ALERT_URL = os.environ.get('ALERT_URL', '') 166 | # Github Configurations (if you choose to store test ymls in github) 167 | 168 | """ 169 | If either GIT_USER OR GIT_PASSWORD OR both are empty then only Public Repository or Organisation can be accessed 170 | If both GIT_USER and GIT_PASSWORD are given then Public/Private Repository or Organisation can be accessed 171 | Please provide only one field either REPO_ORG or REPO_USER 172 | If you want to use private repo of an Organisation then provide REPO_ORG 173 | If you want to use public repo of individual then provide REPO_USER 174 | """ 175 | GIT_USER = os.environ.get('GIT_USER', '') 176 | GIT_PASS = os.environ.get('GIT_PASS', '') 177 | REPO_NAME = os.environ.get('REPO_NAME', '') 178 | BRANCH_NAME = os.environ.get('BRANCH_NAME', 'main') 179 | REPO_ORG = os.environ.get('REPO_ORG', '') 180 | REPO_USER = os.environ.get('REPO_USER', '') 181 | 182 | REPORT_PORTAL_ENABLED = os.environ.get('REPORT_PORTAL_ENABLED',None) 183 | REPORT_PORTAL_ENDPOINT= os.environ.get('REPORT_PORTAL_ENDPOINT','') 184 | REPORT_PORTAL_PROJECT_NAME= os.environ.get('REPORT_PORTAL_PROJECT_NAME','') 185 | REPORT_PORTAL_TOKEN= os.environ.get('REPORT_PORTAL_TOKEN','') 186 | 187 | report_portal_service=None 188 | 189 | if(REPORT_PORTAL_ENABLED!=None and REPORT_PORTAL_ENABLED.lower()== "true"): 190 | try: 191 | report_portal_service = RPClient(endpoint=REPORT_PORTAL_ENDPOINT, 192 | project=REPORT_PORTAL_PROJECT_NAME, 193 | token=REPORT_PORTAL_TOKEN) 194 | 195 | except Exception as e: 196 | raise Exception("Invalid Credentials") 197 | 198 | CRON_ENABLED=os.environ.get('CRON_ENABLED','false') 199 | CRON_FILE_SOURCE=os.environ.get('CRON_FILE_SOURCE','') 200 | CRON_FILE_PATH=os.environ.get('CRON_FILE_PATH','') 201 | from engine.utils.utils import read_yaml 202 | from engine.logs import * 203 | 204 | if ( CRON_ENABLED != None and CRON_ENABLED.lower()=="true"): 205 | 206 | if(CRON_FILE_SOURCE != None and CRON_FILE_SOURCE.lower()=="local"): 207 | try: 208 | cron_job_descriptor_yml = yaml.safe_load(open(CRON_FILE_PATH)) 209 | except: 210 | logger.info("Cron File Path is given incorrectly") 211 | 212 | elif(CRON_FILE_SOURCE != None and CRON_FILE_SOURCE.lower()=="git"): 213 | try: 214 | cron_store="CRON" 215 | os.makedirs(f"{STORAGE_PATH}/{cron_store}",exist_ok=True) 216 | cron_job_descriptor_yml=read_yaml(REPO_NAME,BRANCH_NAME,CRON_FILE_PATH,cron_store) 217 | except: 218 | logger.info("Cron_File_Path/Repo_Name/Branch_Name given incorrectly ") 219 | 220 | cron_jobs_list = [] 221 | 222 | try: 223 | for cron_job in cron_job_descriptor_yml['crons']: 224 | cron_jobs_list.append((f"{cron_job['cron']}", 'daksha.cron.cron_job_executor',[cron_job['params']],{})) 225 | except: 226 | logger.info("Cron Job is not initialised. Rechek the environment variables and the format of yaml file") 227 | 228 | CRONJOBS=cron_jobs_list 229 | 230 | else: 231 | logger.info("Cron jobs not enabled") 232 | -------------------------------------------------------------------------------- /engine/views.py: -------------------------------------------------------------------------------- 1 | """ 2 | Daksha 3 | Copyright (C) 2021 myKaarma. 4 | opensource@mykaarma.com 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Affero General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU Affero General Public License for more details. 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | 16 | """ 17 | 18 | import json 19 | import os 20 | from concurrent.futures.thread import ThreadPoolExecutor 21 | 22 | from django.http import HttpResponse, JsonResponse 23 | from rest_framework import status 24 | 25 | from daksha.settings import REPO_NAME, BRANCH_NAME, TEST_RESULT_DB 26 | from .errors import UnsupportedFileSourceError, BadArgumentsError 27 | from .testreport_generator import * 28 | from .thread_executor import thread_executor 29 | from .utils.utils import read_yaml, read_local_yaml, get_yml_files_in_folder_local, get_yml_files_in_folder_git 30 | from .models import TestResults, GetTestResultsResponse 31 | 32 | 33 | # Create your views here. 34 | def executor(request): 35 | """ 36 | Receives http request and starts execution of Test 37 | 38 | """ 39 | if request.method == 'POST': 40 | try: 41 | test_uuid = generate_test_uuid() 42 | os.makedirs(f"{STORAGE_PATH}/{test_uuid}") 43 | logger.info('Directory created at: ' + f"{STORAGE_PATH}/{test_uuid}") 44 | received_json_data = json.loads(request.body.decode()) 45 | try: 46 | test_ymls, initial_variable_dictionary = __extract_test_data(test_uuid, received_json_data['test']) 47 | except BadArgumentsError as e: 48 | return HttpResponse(str(e), status=status.HTTP_400_BAD_REQUEST) 49 | pool_executor = ThreadPoolExecutor(max_workers=1) 50 | try: 51 | pool_executor.submit(thread_executor, test_ymls, initial_variable_dictionary, test_uuid, 52 | received_json_data['email']) 53 | logger.info("task submitted to thread pool executor") 54 | except Exception as e: 55 | logger.error("Exception occurred", e) 56 | response_message = "Your Test UUID is: " + test_uuid + ". We'll send you an email with report shortly" 57 | return HttpResponse(response_message, status=status.HTTP_200_OK) 58 | except Exception as e: 59 | logger.error("Exception caught", exc_info=True) 60 | return HttpResponse(e, status=status.HTTP_400_BAD_REQUEST) 61 | 62 | else: 63 | return HttpResponse(status=status.HTTP_405_METHOD_NOT_ALLOWED) 64 | 65 | 66 | def testresultsretriever(request, testuuid): 67 | """ 68 | Receives POST request and returns relevant data from the database 69 | 70 | """ 71 | errors = [] 72 | testresults = [] 73 | if request.method == "GET": 74 | try: 75 | logger.info(f"Fetching Test Results from database for TestUUID {testuuid}") 76 | if TEST_RESULT_DB != None and TEST_RESULT_DB.lower() == "postgres": 77 | logger.info("Database Functionality is opted for") 78 | test_results_for_uuid = ( 79 | TestResults.objects.all().filter(TestUUID=testuuid).values() 80 | ) 81 | if not test_results_for_uuid: 82 | logger.info(f"No Test with TestUUID {testuuid} is present in the database") 83 | errors.append( 84 | f"Bad Request : No Test with the TestUUID {testuuid} " 85 | ) 86 | fetched_test_results = GetTestResultsResponse(testresults, errors) 87 | #Since the model GetTestResultsResponse is not serializable, we dump the contents in a string and load it in JSON format 88 | fetched_test_results_json_string = json.dumps( 89 | fetched_test_results.__dict__, default=str 90 | ) 91 | fetched_test_results_json = json.loads( 92 | fetched_test_results_json_string 93 | ) 94 | return JsonResponse( 95 | fetched_test_results_json, status=status.HTTP_400_BAD_REQUEST 96 | ) 97 | 98 | if request.GET.get('testName',None) != None: 99 | testname = request.GET.get('testName') 100 | logger.info(f"User has opted for test results of test {testname} in the TestUUID {testuuid}") 101 | test_result_for_testname = ( 102 | TestResults.objects.all() 103 | .filter(TestUUID=testuuid, TestName=testname) 104 | .values() 105 | ) 106 | if test_result_for_testname: 107 | logger.info(f"Fetching Test result for test name {testname} of TestUUID {testuuid}") 108 | testresults.append(test_result_for_testname[0]) 109 | else: 110 | logger.info(f"No Test in the TestUUID {testuuid} is with TestName {testname} ") 111 | errors.append( 112 | f"Bad Request : No Test in the TestUUID {testuuid} is with TestName {testname} " 113 | ) 114 | else: 115 | logger.info( 116 | f"Since no Test names are provided in the query parameters, All Test in TestUUID {testuuid} would be returned" 117 | ) 118 | for test_result_for_uuid in test_results_for_uuid: 119 | testresults.append(test_result_for_uuid) 120 | 121 | if errors: 122 | testresults.clear() 123 | fetched_test_results = GetTestResultsResponse(testresults, errors) 124 | fetched_test_results_json_string = json.dumps( 125 | fetched_test_results.__dict__, default=str 126 | ) 127 | fetched_test_results_json = json.loads( 128 | fetched_test_results_json_string 129 | ) 130 | logger.error(f"Bad Request : {fetched_test_results_json}") 131 | return JsonResponse( 132 | fetched_test_results_json, status=status.HTTP_400_BAD_REQUEST 133 | ) 134 | else: 135 | fetched_test_results = GetTestResultsResponse(testresults, errors) 136 | fetched_test_results_json_string = json.dumps( 137 | fetched_test_results.__dict__, default=str 138 | ) 139 | fetched_test_results_json = json.loads( 140 | fetched_test_results_json_string 141 | ) 142 | logger.info(f"Returning data : {fetched_test_results_json}") 143 | return JsonResponse(fetched_test_results_json) 144 | 145 | else: 146 | logger.error( "Database Functionality is not opted for.Hence GET request can't be processed") 147 | errors.append( f"Database Functionality is not opted for.Hence GET request can't be processed" ) 148 | fetched_test_results = GetTestResultsResponse(testresults, errors) 149 | fetched_test_results_json_string = json.dumps( fetched_test_results.__dict__, default=str) 150 | fetched_test_results_json = json.loads( fetched_test_results_json_string ) 151 | return JsonResponse( fetched_test_results_json, status=status.HTTP_400_BAD_REQUEST) 152 | except Exception as e: 153 | logger.error("Exception caught", exc_info=True) 154 | errors.append("Exception Caught: " + str(e)) 155 | fetched_test_results = GetTestResultsResponse(testresults, errors) 156 | fetched_test_results_json_string = json.dumps( fetched_test_results.__dict__, default=str) 157 | fetched_test_results_json = json.loads( fetched_test_results_json_string ) 158 | return JsonResponse( fetched_test_results_json, status=status.HTTP_400_BAD_REQUEST) 159 | else: 160 | logger.error("Method not allowed") 161 | errors.append("Method not allowed") 162 | fetched_test_results = GetTestResultsResponse(testresults, errors) 163 | fetched_test_results_json_string = json.dumps( fetched_test_results.__dict__, default=str) 164 | fetched_test_results_json = json.loads( fetched_test_results_json_string ) 165 | return JsonResponse( fetched_test_results_json, status=status.HTTP_405_METHOD_NOT_ALLOWED) 166 | 167 | 168 | def __extract_test_data(test_uuid, test): 169 | initial_variable_dictionary = {} 170 | if "variables" in test: 171 | initial_variable_dictionary = test['variables'] 172 | test_ymls = [] 173 | source_location = test['source'] 174 | location_type = test['type'] 175 | path = test['path'] 176 | if "git" in source_location.lower(): 177 | if "file" == location_type: 178 | files = [path] 179 | else: 180 | files = get_yml_files_in_folder_git(REPO_NAME, BRANCH_NAME, path) 181 | for file_path in files: 182 | test_yml = read_yaml(REPO_NAME, BRANCH_NAME, file_path, test_uuid) 183 | test_ymls.append(test_yml) 184 | 185 | elif "local" in source_location.lower(): 186 | if "file" == location_type: 187 | files = [path] 188 | else: 189 | files = get_yml_files_in_folder_local(path) 190 | for file_path in files: 191 | test_yml = read_local_yaml(file_path) 192 | test_ymls.append(test_yml) 193 | 194 | else: 195 | error_message = "source_location = %s is not supported, please use git or local" % source_location 196 | logger.error(error_message) 197 | raise UnsupportedFileSourceError(error_message) 198 | 199 | return test_ymls, initial_variable_dictionary 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Daksha 2 | =============================================== 3 | 4 | ## Introduction 5 | Daksha is a django framework based dynamic test case execution API.You can write any variation of UI-based test cases in a yml file and execute them right away with this new tool in town.
For example: Sign in to any of your social media account and sign out or sign in then perform a task and then sign out.
The API request should comprise of the location of the yml file.To summarize in simple terms the working can be divided into two steps - 6 | * [Sending API Request](daksha_know-how/ApiRequest.md) 7 | * [Writing Test Case](daksha_know-how/CreateTest.md) 8 | 9 | You can also auto-generate test yamls through Daksha Recorder [chrome extension](recorder/extensions/chrome/ReadMe.md). 10 | 11 | ## Quickstart 12 | #### ⚠️ Warning (This is not compatible with ARM64 architecture.) 13 | 14 | To run your first test case with Daksha, follow the steps in [QuickStart](daksha_know-how/QuickStart.md). 15 | 16 | ## Building and Running 17 | 18 | ### Using docker 19 | 20 | - Setup a local or remote chromedriver for your tests 21 | - Take a look at [docker-compose.yml](docker-compose.yml) file and create all the necessary [environment variables](#environment-variables). 22 | - Run the command `docker-compose up -d` to initiate the build and deploy the project. 23 | 24 | - NOTE: If you're running the application through docker and intend to run scripts saved locally, make sure to mount the location of test yamls to the container so that test yamls could be accessed from inside of container. 25 | 26 | ### Local deployment (without docker) 27 | 28 | - Download [python 3.8+](https://www.python.org/downloads/) and setup a [virtual environment](https://docs.python.org/3/tutorial/venv.html) 29 | - Install all requirements using `pip install -r requirements.txt` 30 | - (Optional) Create any env variables needed. 31 | - Download Chromedriver (https://chromedriver.chromium.org/downloads) 32 | - Run `.\startup_command.bat` to start the project in case of Windows. 33 | - Run `sh startup_command.sh` to start the project in case of Linux. 34 | - Run `sh startup_command_mac.sh` to start the project in case of MacOS. 35 | 36 | ## Setting up webdriver 37 | We only support chromedriver at this point. 38 | 39 | ### Local Webdriver 40 | Download Chromedriver according to your chrome version from https://chromedriver.chromium.org/downloads. You can later refer this path from your test yml. 41 | 42 | ### Remote Webdriver 43 | Setup selenium grid using https://www.selenium.dev/downloads/. Or if you want to use the dockerized version you can download and run selenium grid images from dockerhub: https://hub.docker.com/u/selenium. 44 | 45 | ## Database 46 | - The user can opt for this functionality if he/she wants to save the test results in the database. 47 | - To enable this, please provide the following environment variables:- 48 | `TEST_RESULT_DB`, `PG_DB`, `PG_USER`, `PG_PASSWORD`, `PG_HOST`, `PG_PORT` 49 | 50 | - We only support Postgresql database at this point. To enable this one has to set the environment variable `TEST_RESULT_DB` to `postgres`. 51 | - This functionality is optional and Daksha workflow does not depend on it. 52 | 53 | ### Deploying Daksha with Database enabled by Docker 54 | - Run the command `docker-compose up -d` to initiate the build and deploy the project. 55 | 56 | ### Deploying Daksha with external Postgressql Database 57 | - This assumes that you have an external Postgresql database up and provide the correct environment variables :- 58 | `TEST_RESULT_DB`, `PG_DB`, `PG_USER`, `PG_PASSWORD`, `PG_HOST`, `PG_PORT` 59 | - Comment out the database service in [docker-compose file](./docker-compose.yml). 60 | - Run the command `docker-compose up -d` to initiate the build and deploy the project. 61 | 62 | ## Cron Jobs 63 | - The user can opt for this functionality if he/she wants to run tests at regulated intervals without hitting the api endpoints. 64 | - To enable this the user has to provide an environment variable `CRON_ENABLED` and set it to `true`. 65 | - If `CRON_ENABLED` is `true` then user has to provide additional environment variables `CRON_FILE_SOURCE` 66 | and `CRON_FILE_PATH`. 67 | - The user has to provide the description of Cron Jobs he/she wants to regulate in a YAML file. This yaml file can be loaded locally or from github. In both cases the necessary environment variables have to be set up accordingly. 68 | - Note that the YAML file containing cron jobs description and the YAML files containing the tests to be executed must not be present in same sub folders in the github branch. 69 | - The format of the yaml file containing the cron jobs description is provided in [CronJob Description](daksha_know-how/CronJobsDescription.md) 70 | 71 | - If the user is deploying the application locally, the CronJob functionality is only supported in Unix-like operating systems like Linux and macOS. 72 | - Windows users need to deploy the application using Docker to avail the CronJob functionality. 73 | 74 | ## Receiving Test Result Data 75 | - The user can recieve the test result data by providing TestUUID. 76 | - In order to utilize this functionality, the user must have chosen the database feature and configured the required environment variables. 77 | - To receive the test result data, the user has to hit a GET request at the endpoint:- 78 | - `http://127.0.0.1:8083/daksha/tests/{testuuid}` if the application is deployed through docker. 79 | - `http://127.0.0.1:8000/daksha/tests/{testuuid}` if the application is deployed locally. 80 | - The user has the option to retrieve test result data for all tests associated with a TestUUID or can specify the desired test name in the query parameters as :- 81 | - `http://127.0.0.1:8083/daksha/tests/{testuuid}?testName={testName}` if the application is deployed through docker. 82 | - `http://127.0.0.1:8000/daksha/tests/{testuuid}?testName={testName}` if the application is deployed locally. 83 | - Different possibles responses are:- 84 | - Status Code 405 : Method not allowed 85 | - Please recheck that the method of the request is GET. 86 | - Status Code 400 : Bad Request 87 | - Please recheck that the TestUUID is entered correctly. 88 | - Please recheck that the Test Names provided in the query parameters are given correctly. 89 | - Please recheck that the database functionality is opted for and all the necessary environment variables are set. 90 | - Status Code 404 : Page not found 91 | - Please recheck that the correct endpoint is being hit. 92 | 93 | ## Report Portal 94 | - Users can choose to integrate Daksha test reports with [Report Portal](https://github.com/reportportal). 95 | - To enable this integration, users need to provide specific environment variables: 96 | `REPORT_PORTAL_ENABLED`, `REPORT_PORTAL_PROJECT_NAME`, `REPORT_PORTAL_ENDPOINT`, `REPORT_PORTAL_TOKEN`. 97 | - To enable this functionality, set the value of `REPORT_PORTAL_ENABLED` to `True`. 98 | - User must have deployed Report Portal through building the [Report Portal Docker Compose file](https://github.com/reportportal/reportportal/blob/master/docker-compose.yml). 99 | - The user can also use the same Postgres server for Report Portal and Daksha.To enable this, Please edit the [Report Portal Docker Compose file](https://github.com/reportportal/reportportal/blob/master/docker-compose.yml) and expose the ports of the Postgres Database before initiating the build. 100 | - This can be done by uncommenting the ports section for postgres service defined in the yml file. 101 | - Users must ensure that the Report Portal service is deployed and that the environment variable values align with the deployed service. 102 | - Run the command `docker-compose up -d` to initiate the build and deploy the project. 103 | 104 | ## #Environment Variables 105 | You can configure the application in a lot of ways by setting the following environment variables: 106 | * **STORAGE_PATH** 107 | * This is the location of directory where reports generated by the application are stored. 108 | * Defaults to 'reports' in current directory. 109 | 110 | * **APACHE_URL** 111 | * If you're using a hosting service (like apache) to host your reports, put the base URL here. 112 | * Make sure that this base url is mapped to whatever location the reports are stored. (governed by **STORAGE_PATH** environment variable) 113 | 114 | * **LOG_FILE** 115 | * This is the location where reports generated by the application are stored. 116 | * Defaults to 'logs/uiengine.log' in current directory. 117 | 118 | * **POSTMARK_TOKEN** 119 | * This is the postmark token which will be used to send reports by email. 120 | * If not set, we'll skip sending the email. 121 | 122 | * **ALERT_URL** 123 | * This is the alert webhook url which will be used to send alerts in gchat/slack. 124 | * If not set, we'll skip sending the alert. 125 | 126 | * **EMAIL_HOST_USER** 127 | * This is the email ID from which reports should be sent. 128 | 129 | * **GIT_USER** 130 | * Github username for logging into github to fetch test yamls. 131 | * Don't set it/Leave it blank if the repo is public and no authentication is needed. 132 | 133 | * **GIT_PASS** 134 | * Github password for logging into github to fetch test yamls. 135 | * Don't set it/Leave it blank if the repo is public and no authentication is needed. 136 | 137 | * **REPO_USER** 138 | * Name of user who owns the github repository containing the test yamls. 139 | * Only one of the variables **REPO_USER** and **REPO_ORG** should be set at a time. 140 | 141 | * **REPO_ORG** 142 | * Name of organization which owns the github repository containing the test yamls. 143 | * Only one of the variables **REPO_USER** and **REPO_ORG** should be set at a time. 144 | 145 | * **REPO_NAME** 146 | * Name of the github repository containing the test yamls. 147 | 148 | * **BRANCH_NAME** 149 | * Name of the branch containing the test yamls in the github repository defined by **REPO_NAME**. 150 | 151 | * **DJANGO_SECRET_KEY** 152 | * Use this variable if you want to override the default value of SECRET_KEY for added security. 153 | 154 | * **ALLOWED_HOSTS** 155 | * Provide a comma separated list of hosts which you'd like to be added to ALLOWED_HOSTS. 156 | 157 | * **TEST_RESULT_DB** 158 | * If you want to use the Postgresql database to save your test reports, create this environment variable and set its value to `postgres`. 159 | * If you set this value as `postgres`, you need to provide additional environment variables. 160 | * If you don't want the database functionality, delete this environment variable or set it to disabled. 161 | 162 | * **PG_DB** 163 | * Name of the database. If this value is not provided, the default name of database will be `postgres`. 164 | 165 | * **PG_USER** 166 | * Name of the User. If this value is not provided, the default name of user will be `postgres`. 167 | 168 | * **PG_PASSWORD** 169 | * Password corresponding to the user. Default password for user postgres is `postgres`. 170 | 171 | * **PG_HOST** 172 | * The host of our database. If this value is not provided, the default host is `localhost`. 173 | 174 | * **PG_PORT** 175 | * Port provided to the database. If this value is not provided, the default port will be `5432`. 176 | 177 | * **CRON_ENABLED** 178 | * If you want to run tests at regulated intervals, set this variable to `true`. 179 | * If you don't want to run cron jobs, delete this environment variable or set it to `false`. 180 | 181 | * **CRON_FILE_SOURCE** 182 | * This value can either be `local` or `git`. It denotes the source of yaml file which contains Cron jobs description. 183 | 184 | * **CRON_FILE_PATH** 185 | * This value should be set to the path of the yaml file which contains cron job description. 186 | 187 | * **REPORT_PORTAL_ENABLED** 188 | * This value should be set to `True` if the user wants the Test reports to be displayed in Report Portal. 189 | 190 | * **REPORT_PORTAL_ENDPOINT** 191 | * This value should be set to the URL of the ReportPortal server where the client should connect to. 192 | 193 | * **REPORT_PORTAL_PROJECT_NAME** 194 | * This values should be set to the name of the specific project or workspace within ReportPortal where the user wants to show the Daksha test reports. 195 | 196 | * **REPORT_PORTAL_TOKEN** 197 | * This value should match the authentication token provided by the user-deployed ReportPortal.Please refer [Token to be used by Report Portal Client Tools](https://reportportal.io/docs/reportportal-configuration/HowToGetAnAccessTokenInReportPortal/#2-authorization-with-users-uuid-access-token-for-agents) 198 | 199 | ## Get in Touch 200 | 201 | * Open an issue at: https://github.com/mykaarma/daksha/issues 202 | * Email us at: opensource@mykaarma.com 203 | 204 | ## Contributions 205 | 206 | We welcome contributions to Daksha. Please see our [contribution guide](CONTRIBUTING.md) for more details. 207 | 208 | ## License 209 | Copyright 2021 myKaarma 210 | 211 | Licensed under the [GNU Affero General Public License v3](LICENSE) 212 | 213 | ## Motivation 214 | 215 | [Information Modal](https://github.com/mykaarma/information-modal) 216 | -------------------------------------------------------------------------------- /recorder/extensions/chrome/src/ContentScript.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Daksha 4 | Copyright (C) 2021 myKaarma. 5 | opensource@mykaarma.com 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Affero General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Affero General Public License for more details. 14 | You should have received a copy of the GNU Affero General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | 19 | let previousSelectedElementXpath = ""; 20 | let previousTargetElement; 21 | // For parent window previousIframe === "" ; 22 | let previousIframe = ""; 23 | let currentIframe = ""; 24 | let pauseVal = true; 25 | let leftClickPressed = 0; 26 | let dakshaYamlStorageKey = "generatedDakshaYaml"; 27 | let pauseValueStorageKey = "updatePauseValue"; 28 | let popupPageStorageKey = "popupPageNumber"; 29 | let playPauseStorageKey = "playPauseIcon"; 30 | let dakshaYamlFormatterRegex = />+-+\s*/g; 31 | let yaml = require("js-yaml"); 32 | let RecordingCoordinates; 33 | let recordingButton; 34 | require('@webcomponents/custom-elements'); 35 | class WordCount extends HTMLElement { 36 | constructor() { 37 | super(); 38 | } 39 | 40 | connectedCallback() { 41 | const shadow = this.attachShadow({ mode: 'open' }); 42 | this.style.border = 'none'; 43 | this.style.zIndex = '1000'; 44 | this.style.position = 'absolute'; 45 | 46 | this.style.top = `${RecordingCoordinates[1]}px`; 47 | this.style.left = `${RecordingCoordinates[0]}px`; 48 | this.style.height = '35px'; 49 | this.style.width = '140px'; 50 | let ifrm = document.createElement('iframe') 51 | ifrm.style.border = 'none'; 52 | ifrm.style.position = 'relative'; 53 | ifrm.style.height = '100%'; 54 | ifrm.style.width = '100%'; 55 | ifrm.style.padding = "0px"; 56 | ifrm.style.boxShadow = "2px 4px 6px silver"; 57 | ifrm.style.borderRadius = "5px"; 58 | ifrm.src = chrome.runtime.getURL("./RecordingTab.html"); 59 | 60 | shadow.appendChild(ifrm); 61 | } 62 | } 63 | let createRecordingButton = () => { 64 | recordingButton.style.display = ''; 65 | } 66 | let removeRecordingButton = () => { 67 | recordingButton.style.display = 'none'; 68 | 69 | } 70 | let draggabilityFunctionalityForrecordingDiv = () => { 71 | recordingButton = document.createElement('daksha-recorder'); 72 | recordingButton.style.display = 'none'; 73 | document.body.parentNode.appendChild(recordingButton); 74 | var dragItem = recordingButton 75 | var active = false; 76 | var currentX; 77 | var currentY; 78 | var initialX; 79 | var initialY; 80 | var startingX; 81 | var startingY; 82 | let xOffset = 0; 83 | var yOffset = 0; 84 | let dragStart = function (x, y) { 85 | initialX = x - xOffset; 86 | initialY = y - yOffset; 87 | active = true; 88 | } 89 | 90 | let dragEnd = function (e) { 91 | active = false; 92 | } 93 | let availableHeight = window.innerHeight; 94 | let totalHeight = window.screen.availHeight; 95 | let heightOfIframe = dragItem.offsetHeight; 96 | let drag = function (e) { 97 | 98 | if (active && e.screenY > totalHeight - availableHeight + heightOfIframe && e.screenY < totalHeight - heightOfIframe 99 | && e.screenX > 20 && e.screenX + 50 < window.innerWidth 100 | ) { 101 | currentX = e.screenX - initialX; 102 | currentY = e.screenY - initialY; 103 | xOffset = currentX; 104 | yOffset = currentY; 105 | 106 | setTranslate(currentX, currentY, dragItem); 107 | } 108 | } 109 | 110 | let setTranslate = function (xPos, yPos, el) { 111 | el.style.transform = "translate3d(" + xPos + "px, " + yPos + "px, 0)"; 112 | } 113 | window.addEventListener('message', (e) => { 114 | 115 | if (e.data.type === "mousedown") { 116 | dragStart(e.data.x, e.data.y); 117 | startingX = e.data.x; 118 | startingY = e.data.y; 119 | } 120 | else if (e.data.type === "mousemove") { 121 | let obj = { screenX: e.data.x, screenY: e.data.y }; 122 | drag(obj); 123 | } 124 | else if (e.data.type === "mouseup") { 125 | let obj = { screenX: e.data.x, screenY: e.data.y }; 126 | dragEnd(obj); 127 | RecordingCoordinates[0] = obj.screenX - (startingX - RecordingCoordinates[0]); 128 | RecordingCoordinates[1] = obj.screenY - (startingY - RecordingCoordinates[1]); 129 | var dakshaYamlObject = {}; 130 | dakshaYamlObject["RecordingCoordinates"] = RecordingCoordinates; 131 | chrome.storage.sync.set(dakshaYamlObject, () => { 132 | }); 133 | } 134 | }); 135 | } 136 | chrome.storage.sync.get(popupPageStorageKey, function (result) { 137 | var popupKey = result[popupPageStorageKey] ? result[popupPageStorageKey] : 1; 138 | var dakshaYamlObject = {}; 139 | dakshaYamlObject[popupPageStorageKey] = popupKey; 140 | chrome.storage.sync.set(dakshaYamlObject, () => { 141 | }); 142 | }); 143 | chrome.storage.sync.get(playPauseStorageKey, function (result) { 144 | var playPauseKey = result[playPauseStorageKey] ? result[playPauseStorageKey] : 1; 145 | var dakshaYamlObject = {}; 146 | dakshaYamlObject[playPauseStorageKey] = playPauseKey; 147 | chrome.storage.sync.set(dakshaYamlObject, () => { 148 | }); 149 | }); 150 | chrome.storage.sync.get("RecordingCoordinates", function (result) { 151 | RecordingCoordinates = result["RecordingCoordinates"]; 152 | customElements.define('daksha-recorder', WordCount); 153 | draggabilityFunctionalityForrecordingDiv(); 154 | }); 155 | 156 | // Function to find XPath of a given Element in a given Document 157 | function getXPath(selectedElement, selectedElementDocument) { 158 | 159 | var allNodes = selectedElementDocument.getElementsByTagName('*'); 160 | for (var segs = []; selectedElement.parentNode && selectedElement.nodeType == 1; selectedElement = selectedElement.parentNode) { 161 | if (selectedElement.hasAttribute('id')) { 162 | var uniqueIdCount = 0; 163 | for (var n = 0; n < allNodes.length; n++) { 164 | if (allNodes[n].hasAttribute('id') && allNodes[n].id == selectedElement.id) uniqueIdCount++; 165 | if (uniqueIdCount > 1) break; 166 | }; 167 | if (uniqueIdCount == 1) { 168 | segs.unshift('//' + selectedElement.localName.toLowerCase() + `[@id='${selectedElement.getAttribute('id')}']`); 169 | return segs.join('/'); 170 | } else { 171 | segs.unshift(selectedElement.localName.toLowerCase() + `[@id='${selectedElement.getAttribute('id')}']`); 172 | } 173 | } 174 | else { 175 | for (i = 1, siblingsOfSelectedElement = selectedElement.previousSibling; siblingsOfSelectedElement; siblingsOfSelectedElement = siblingsOfSelectedElement.previousSibling) { 176 | if (siblingsOfSelectedElement.localName == selectedElement.localName) i++; 177 | }; 178 | segs.unshift(selectedElement.localName.toLowerCase() + '[' + i + ']'); 179 | }; 180 | }; 181 | return segs.length ? '//' + segs.join('/') : null; 182 | }; 183 | // This function is used to update the Daksha Yaml and Add new dakshaYamlObjectss. 184 | function updateDakshaYamlFile(dakshaYamlObjects) { 185 | 186 | chrome.storage.sync.get(dakshaYamlStorageKey, function (result) { 187 | var array = result[dakshaYamlStorageKey] ? result[dakshaYamlStorageKey] : []; 188 | array.push.apply(array, dakshaYamlObjects); 189 | var dakshaYamlObject = {}; 190 | dakshaYamlObject[dakshaYamlStorageKey] = array; 191 | chrome.storage.sync.set(dakshaYamlObject, () => { 192 | }); 193 | }); 194 | } 195 | // Defined the dakshaYamlObjectss which will be added into the updateDakshaYamlFile function!! 196 | let fill_data = (xpath, value) => { 197 | let json_obj = { 198 | "fill_data": { 199 | "xpath": `${xpath}`, 200 | "value": `${value}` 201 | } 202 | }; 203 | return json_obj; 204 | } 205 | let hard_wait = (sec) => { 206 | let json_obj = { 207 | "wait_for": { 208 | "mode": 'hardwait', 209 | "value": sec 210 | } 211 | } 212 | 213 | return json_obj; 214 | } 215 | let switch_iframe = (xpath) => { 216 | let json_obj = { 217 | "switch_iframe": { 218 | "xpath": `${xpath}` 219 | } 220 | }; 221 | 222 | return json_obj; 223 | } 224 | let switch_to_default_iframe = () => { 225 | let json_obj = 226 | "switch_to_default_iframe" 227 | 228 | return json_obj; 229 | } 230 | let click_button = (xpath) => { 231 | let json_obj = { 232 | "click_button": { 233 | "xpath": `${xpath}` 234 | } 235 | }; 236 | 237 | return json_obj; 238 | }; 239 | let open_url = (url) => { 240 | let json_obj = { 241 | "open_url": { 242 | "url": `${url}` 243 | } 244 | }; 245 | 246 | return json_obj; 247 | }; 248 | function getDakshaEventsArray(event) { 249 | // Adding objects to the dakshaYamlObjects array to push them updateDakshaYamlFile function 250 | let dakshaYamlObjects = []; 251 | if (previousSelectedElementXpath !== "") { 252 | if (previousTargetElement !== null && previousTargetElement !== undefined) { 253 | let previousTargetElementTagName = previousTargetElement.tagName.toLowerCase(); 254 | let previousTargetElementValue = previousTargetElement.value; 255 | if ((previousTargetElementTagName === "input" || previousTargetElementTagName === "textarea")) { 256 | dakshaYamlObjects.push(fill_data(previousSelectedElementXpath, previousTargetElementValue)); 257 | } 258 | } 259 | } 260 | let targetElement = event.target; 261 | previousTargetElement = targetElement; 262 | let selectedElementDocument = event.view.document; 263 | let XPath = getXPath(targetElement, selectedElementDocument); 264 | previousSelectedElementXpath = XPath; 265 | dakshaYamlObjects.push(click_button(XPath)); 266 | return dakshaYamlObjects; 267 | } 268 | function eventHandlerInIframe(event, iframeXPath) { 269 | //Handling the events occurred inside the Iframes 270 | if (event.button === leftClickPressed) { 271 | if (pauseVal === false) { 272 | let dakshaYamlObjects = []; 273 | let selectedIframeXpath = iframeXPath; 274 | previousIframe = currentIframe; 275 | currentIframe = `${selectedIframeXpath}`; 276 | if (previousIframe != currentIframe) { 277 | if (previousIframe === "") { 278 | dakshaYamlObjects.push(switch_iframe(selectedIframeXpath)); 279 | } 280 | else { 281 | dakshaYamlObjects.push(switch_to_default_iframe()); 282 | dakshaYamlObjects.push(switch_iframe(selectedIframeXpath)); 283 | } 284 | 285 | } 286 | let dakshaEventsArray = getDakshaEventsArray(event); 287 | dakshaYamlObjects.push.apply(dakshaYamlObjects, dakshaEventsArray); 288 | updateDakshaYamlFile(dakshaYamlObjects); 289 | } 290 | } 291 | } 292 | function AddEventListenerToAllIframe(document) { 293 | //Adding Event Listener to all iframes 294 | let allIframe = document.getElementsByTagName("iframe"); 295 | Array.prototype.slice.call(allIframe).forEach(iframe => { 296 | let iwindow = iframe.contentWindow; 297 | let iframeXPath = getXPath(iframe, document); 298 | iframe.onload = () => { 299 | iwindow.addEventListener('mousedown', (e) => eventHandlerInIframe(e, iframeXPath)); 300 | } 301 | }); 302 | } 303 | 304 | function getYamlFileData(array) { 305 | var dakshaYamlObjects = { 306 | "config": { 307 | "env": "", 308 | "browser": "", 309 | "driverAddress": "" 310 | }, 311 | "name": "", 312 | "alert_type": "", 'task': array 313 | }; 314 | let data = yaml.dump(JSON.parse(JSON.stringify(dakshaYamlObjects))); 315 | let regexFormattedData = data.replace(dakshaYamlFormatterRegex, ''); 316 | return regexFormattedData; 317 | 318 | } 319 | function resetAndStartAgain() { 320 | chrome.storage.sync.remove(dakshaYamlStorageKey, () => { }); 321 | previousSelectedElementXpath = ""; 322 | previousIframe = ""; 323 | currentIframe = ""; 324 | previousTargetElement = null; 325 | var dakshaYamlObject = {}; 326 | dakshaYamlObject[pauseValueStorageKey] = true; 327 | chrome.storage.sync.set(dakshaYamlObject, () => { 328 | }); 329 | pauseVal = true; 330 | 331 | } 332 | 333 | // Fetching the information whether the chrome extension is pauseVald or not 334 | chrome.storage.sync.get(pauseValueStorageKey, function (result) { 335 | if (result[pauseValueStorageKey] !== null) { 336 | pauseVal = result[pauseValueStorageKey]; 337 | if (pauseVal === false) { 338 | createRecordingButton(); 339 | } 340 | } 341 | }) 342 | //Listening to the mutation to add dakshaEventsArrayeners of iframes. 343 | 344 | let mutationObserver = new MutationObserver(function (mutations) { 345 | AddEventListenerToAllIframe(document); 346 | }); 347 | mutationObserver.disconnect() ; 348 | mutationObserver.observe(document, { 349 | attributes: true, 350 | characterData: true, 351 | childList: true, 352 | subtree: true, 353 | attributeOldValue: true, 354 | characterDataOldValue: true 355 | }); 356 | 357 | // Adding mousdown dakshaEventsArrayener to window. 358 | window.addEventListener('mousedown', (event) => { 359 | if (event.button === leftClickPressed) { 360 | if (pauseVal === false) { 361 | let dakshaYamlObjects = []; 362 | previousIframe = currentIframe; 363 | currentIframe = ""; 364 | if (previousIframe != currentIframe) { 365 | dakshaYamlObjects.push(switch_to_default_iframe()); 366 | } 367 | let dakshaEventsArray = getDakshaEventsArray(event); 368 | dakshaYamlObjects.push.apply(dakshaYamlObjects, dakshaEventsArray); 369 | updateDakshaYamlFile(dakshaYamlObjects); 370 | } 371 | } 372 | }) 373 | 374 | // Listening to the Context Menu Clicks. 375 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 376 | if (request.type === "copy_to_clipboard") { 377 | chrome.storage.sync.get(dakshaYamlStorageKey, function (result) { 378 | var array = result[dakshaYamlStorageKey]; 379 | let data = getYamlFileData(array); 380 | navigator.clipboard.writeText(data).then(() => { 381 | alert('All your data has been Copied to clipboard , You can now start again!'); 382 | }) 383 | .catch((e) => { 384 | console.log(e); 385 | }) 386 | }); 387 | resetAndStartAgain(); 388 | } 389 | else if (request.type === "download") { 390 | chrome.storage.sync.get(dakshaYamlStorageKey, function (result) { 391 | var array = result[dakshaYamlStorageKey]; 392 | let data = getYamlFileData(array); 393 | let blob = new Blob([data], { type: 'application/yaml' }); 394 | let url = URL.createObjectURL(blob); 395 | let windowOfNewTab = window.open("https://www.google.com"); 396 | var anchorTag = windowOfNewTab.document.createElement('a'); 397 | anchorTag.href = url; 398 | anchorTag.download = "daksha.yaml"; 399 | anchorTag.style.display = 'none'; 400 | windowOfNewTab.document.body.appendChild(anchorTag); 401 | anchorTag.click(); 402 | delete anchorTag; 403 | }); 404 | resetAndStartAgain(); 405 | alert('All your data has been Downloaded , You can now start again!'); 406 | } 407 | else if (request.type === "pause") { 408 | var dakshaYamlObject = {}; 409 | dakshaYamlObject[pauseValueStorageKey] = true; 410 | chrome.storage.sync.set(dakshaYamlObject, () => { 411 | }); 412 | pauseVal = true; 413 | removeRecordingButton(); 414 | } 415 | else if (request.type === "start") { 416 | chrome.storage.sync.remove(dakshaYamlStorageKey, () => { }); 417 | previousSelectedElementXpath = ""; 418 | previousIframe = ""; 419 | currentIframe = ""; 420 | previousTargetElement = null; 421 | var dakshaYamlObject = {}; 422 | dakshaYamlObject[pauseValueStorageKey] = false; 423 | chrome.storage.sync.set(dakshaYamlObject, () => { 424 | }); 425 | pauseVal = false; 426 | updateDakshaYamlFile([open_url(request.msg)]); 427 | createRecordingButton(); 428 | } 429 | else if (request.type === "resume") { 430 | var dakshaYamlObject = {}; 431 | dakshaYamlObject[pauseValueStorageKey] = false; 432 | chrome.storage.sync.set(dakshaYamlObject, () => { 433 | }); 434 | pauseVal = false; 435 | createRecordingButton(); 436 | } 437 | else if (request.type === "customSecondsWait") { 438 | if (request.sec === undefined) { 439 | alert("Kindly enter a numberic value !"); 440 | } 441 | else { 442 | var secondsInInt = parseInt(request.sec); 443 | updateDakshaYamlFile([hard_wait(secondsInInt)]); 444 | alert(`${request.sec} seconds hard wait added!`); 445 | } 446 | } 447 | else if (request.type === "tenSecondsWait") { 448 | var secs = 10; 449 | updateDakshaYamlFile([hard_wait(secs)]); 450 | alert("10 secs hard wait added!"); 451 | } 452 | else if (request.type === "stop") { 453 | if (pauseVal === false) { 454 | var dakshaYamlObject = {}; 455 | dakshaYamlObject[pauseValueStorageKey] = true; 456 | chrome.storage.sync.set(dakshaYamlObject, () => { 457 | }); 458 | pauseVal = true; 459 | removeRecordingButton(); 460 | }; 461 | } 462 | else if (request.type === "viewYaml") { 463 | chrome.storage.sync.get(dakshaYamlStorageKey, function (result) { 464 | var array = result[dakshaYamlStorageKey]; 465 | let data = getYamlFileData(array); 466 | var iframe = `` 467 | var x = window.open("", "", "_blank"); 468 | x.document.open(); 469 | x.document.write(iframe); 470 | }); 471 | } 472 | else if (request.type === "undoLastStep") { 473 | chrome.storage.sync.get(dakshaYamlStorageKey, function (result) { 474 | var array = result[dakshaYamlStorageKey]; 475 | let length = array.length; 476 | if (length > 1) { 477 | array.pop(); 478 | var dakshaYamlObject = {}; 479 | dakshaYamlObject[dakshaYamlStorageKey] = array; 480 | chrome.storage.sync.set(dakshaYamlObject, () => { 481 | }); 482 | } 483 | }); 484 | alert("Last step has been removed!"); 485 | } 486 | 487 | sendResponse({ msg: "Request Processed" }); 488 | return Promise.resolve("Dummy response to keep the console quiet"); 489 | }); --------------------------------------------------------------------------------