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 | });
--------------------------------------------------------------------------------