Skip to content

Commit

Permalink
Merge branch 'stage' into 'master'
Browse files Browse the repository at this point in the history
fix (tree-view): duplicate feature id fix

See merge request cometa/cometa!226
  • Loading branch information
ArslanSB committed Oct 22, 2023
2 parents 02e369f + 52d2ebe commit 2caa770
Show file tree
Hide file tree
Showing 50 changed files with 4,574 additions and 226 deletions.
140 changes: 8 additions & 132 deletions backend/behave/behave_django/schedules/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ def run_test(request):
logger.debug('Environment Variables: {}'.format(VARIABLES))
PROXY_USER = request.POST['HTTP_PROXY_USER'] # user who executed the testcase
logger.debug('Executed By: {}'.format(PROXY_USER))
browsers = json.loads(request.POST['browsers']) # browsers list of the feature
logger.debug('Browsers: {}'.format(browsers))
executions = json.loads(request.POST['browsers']) # browsers list of the feature
logger.debug('Executions: {}'.format(executions))
feature_id = request.POST['feature_id'] # id of the feature that is being executed
logger.debug('Feature id: {}'.format(feature_id))
department = request.POST['department'] # department where the feature belongs, set in request so we can get the department settings
Expand All @@ -94,104 +94,22 @@ def run_test(request):
'department': department,
'feature_id': feature_id
}
"""
os.environ['feature_run'] = str(feature_run)
os.environ['X_SERVER'] = X_SERVER
os.environ['PROXY_USER'] = PROXY_USER
os.environ['VARIABLES'] = VARIABLES
os.environ['PARAMETERS'] = PARAMETERS
os.environ['department'] = department
"""

# Loads user data
user_data = json.loads(PROXY_USER)

# Prefetch Browserstack browsers and local
# Only if some browser has 'latest' in browser_version
logger.debug("Prefetching browsers from local and BrowserStack in case browsers contain 'latest' as browser version.")
cloud_browsers = []
local_browsers = []
for browser in browsers:
#3013: Fix missing cloud property in outdated favourited browsers
if 'cloud' not in browser and browser.get("os","").lower() == "generic":
browser['cloud'] = "local"
# Check selected cloud is local and we don't have it yet
if browser.get('cloud') == 'local' and len(local_browsers) == 0:
logger.debug("Found 'local' as cloud for browser. Getting local browsers.")
# Get local browsers
response = requests_retry.get('http://cometa_django:8000/api/browsers/', headers={'Host': 'cometa.local'})
# Save downloaded browsers
local_browsers = list(map(lambda x: x.get('browser_json'), response.json()))
logger.debug('Response status code from local browsers api: {}'.format(response.status_code))
# Check selected cloud is browserstack and we don't have it yet
elif len(cloud_browsers) == 0:
logger.debug("Found 'cloud' as cloud for browser. Getting BrowserStack browsers.")
response = requests_retry.get('http://cometa_django:8000/browsers/browserstack/', headers={'Host': 'cometa.local'})
cloud_browsers = response.json()['results']
logger.debug('Response status code from BrowserStack browsers api: {}'.format(response.status_code))

# Generate random hash for image url obfuscating
run_hash = secrets.token_hex(nbytes=8)

# Initialize Thread Pool with one worker for each browser
logger.debug('Creating a thread pool to execute testcase in parallel.')
# Return completed run if not browsers are found
if len(browsers) == 0:
logger.debug('Feature has no browser assigned.')
requests.post('http://cometa_socket:3001/feature/%d/runCompleted' % int(feature_id), json={
'type': '[WebSockets] Completed Feature Run',
'run_id': int(feature_run),
'datetime': datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
'user_id': user_data['user_id']
})
return JsonResponse({}, status = 200)

# save all the jobs
jobs = []

# create a thread for each browser
for browser in browsers:
for execution in executions:
browser = execution['browser']
feature_result_id = execution['feature_result_id']
run_hash = execution['run_hash']

logger.debug("Execution testcase in browser: {}".format(browser))
# Check browser for valid values and repairs outdated versions
try:
browser = checkBrowser(browser, local_browsers, cloud_browsers)
except Exception as err:
# Show error in Live Steps Viewer
requests.post('http://cometa_socket:3001/feature/%s/error' % feature_id, data={
"browser_info": json.dumps(browser),
"feature_result_id": 0,
"run_id": feature_run,
"datetime": datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
"error": str(err),
"user_id": user_data['user_id']
})
# Continue on next browser, skipping current browser execution
continue
# dump the json as string
browser = json.dumps(browser)
# data to create a feature_result for the current session
data = {
"feature_id": feature_id,
"result_date": datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ'),
"browser": browser,
"feature_run": feature_run,
"executed_by": user_data['user_id'],
"running": True,
"run_hash": run_hash
}
# send the request with the data
response = requests_retry.post('http://cometa_django:8000/api/feature_results/', headers={'Host': 'cometa.local'}, data=data)
if response.status_code != 201:
# Something went wrong while creating a feature result for current browser
# This can be due to a Server Error
logger.error('Unable to retrieve feature_result_id for current browser execution.')
exit
# save the feature_result_id to the environment variable
try:
feature_result_id = str(response.json()['feature_result_id'])
except json.decoder.JSONDecodeError as err:
logger.error('Failed to parse Django response as JSON. See Sentry for details.')
capture_exception(err)
# send websocket about the feature has been queued
request = requests.get('http://cometa_socket:3001/feature/%s/queued' % feature_id, data={
"user_id": user_data['user_id'],
Expand All @@ -202,7 +120,7 @@ def run_test(request):
})
# add missing variables to environment_variables dict
environment_variables['BROWSER_INFO'] = browser
environment_variables['feature_result_id'] = feature_result_id
environment_variables['feature_result_id'] = str(feature_result_id)
environment_variables['RUN_HASH'] = run_hash
# Add the current browser to the thread pool
job = django_rq.enqueue(
Expand All @@ -221,48 +139,6 @@ def run_test(request):

# Wait for the thread pool to finish
return JsonResponse({}, status = 200)

# Checks if the selected browser is valid using the array of available browsers
# for the selected cloud, it also tries to repair the selected browser object
def checkBrowser(selected, local_browsers, cloud_browsers):
# Retrieve all browsers from either local or browserstack
browsers = local_browsers if selected.get('cloud', 'browserstack') == 'local' else cloud_browsers
if selected.get('cloud', '') == 'local' and selected.get('mobile_emulation', False):
# In that case just assign the latest version of Chrome for the emulated browser
# Exclude non stable versions by checking if version can be numeric (without dots)
browsers = [ x for x in browsers if x.get('browser_version').replace('.', '').isnumeric() and x.get('browser') == 'chrome' ]
# Sort browsers by browser_version
browsers = sorted(browsers, key=lambda k: int(k.get('browser_version').replace('.', '')), reverse=True)
version = browsers[0].get('browser_version')
selected['browser_version'] = version
selected['mobile_user_agent'] = selected['mobile_user_agent'].replace('$version', version)
selected['browser'] = 'chrome'
return selected
# Set common browser properties
fields = ['browser', 'device', 'os', 'os_version', 'real_mobile']
# Filter corresponding browsers with properties from selected browser
# If the resulting array contains 0 elements, it means the selected browser
# is not found, and therefore is invalid
for field in fields:
browsers = [ x for x in browsers if x.get(field) == selected.get(field) ]
if len(browsers) == 0:
raise Exception('Found invalid browser object')
# Filter browsers by the version in the selected browser
# If the resulting array contains 0 elements, it means the selected browser
# has an outdated browser version or invalid, in that case
browsers_versions = [ x for x in browsers if x.get('browser_version') == selected.get('browser_version') ]
# Handle exception of "latest" version, it is handled later
if selected.get('browser_version', '') == "latest" or len(browsers_versions) == 0:
# Exclude non stable versions by checking if version can be numeric (without dots)
browsers = [ x for x in browsers if x.get('browser_version').replace('.', '').isnumeric() ]
# Sort browsers by browser_version
browsers = sorted(browsers, key=lambda k: int(k.get('browser_version').replace('.', '')), reverse=True)
try:
# Assign the version of the first sorted browser
selected['browser_version'] = browsers[0].get('browser_version')
except:
raise Exception('Unable to find latest version for the selected browser')
return selected

@require_http_methods(["GET"])
@csrf_exempt
Expand Down
60 changes: 55 additions & 5 deletions backend/behave/cometa_itself/steps/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,21 @@ def dynamicDateGenerator(content: str):
content = re.sub(pattern, finalDate, content)
return content

def reset_element_highlight(context):
try:
if 'highlighted_element' in context:
element = context.highlighted_element['element']
send_step_details(context, 'Resetting Highligthed element')
context.browser.execute_script('''
const element = arguments[0];
element.style.outline = window.cometa.oldOutlineValue;
element.style.outlineOffset = window.cometa.oldOutlineOffsetValue;
''', element)
del context.highlighted_element
except Exception as err:
logger.exception(err)


# ########################################################################## #
# decorator to ease up making of steps ##################################### #
# this decorator will reduce the code ###################################### #
Expand Down Expand Up @@ -249,6 +264,8 @@ def execute(*args, **kwargs):
# return True meaning everything went as expected
return result
except Exception as err:
# reset timeout incase of exception in function
signal.alarm(0)
# print stack trace
traceback.print_exc()
# set the error message to the step_error inside context so we can pass it through websockets!
Expand Down Expand Up @@ -280,6 +297,9 @@ def execute(*args, **kwargs):
else:
# fail the feature
raise AssertionError(str(err))
finally:
# reset element highligh is any
reset_element_highlight(args[0])
# if the user gets here means that something went wrong somewhere.
return False
return execute
Expand Down Expand Up @@ -1378,6 +1398,29 @@ def step_iml(context, css_selector):
send_step_details(context, 'Focusing on element')
context.browser.execute_script('let elem=document.querySelector("'+css_selector+'"); elem.scrollIntoView(); elem.focus()')

# Highlight element
@step(u'Highlight element with "{selector}"')
@done(u'Highlight element with "{selector}"')
def step_iml(context, selector):
send_step_details(context, 'Looking for selector')
element = waitSelector(context, "css", selector)
if isinstance(element, list) and len(element) > 0:
element = element[0]
send_step_details(context, 'Highlighting the element')
context.browser.execute_script('''
const element = arguments[0];
if (!window.cometa) window.cometa = {}
window.cometa.oldOutlineValue = element.style.outline;
window.cometa.oldOutlineOffsetValue = element.style.outlineOffset;
element.style.outline = "2px solid #f00";
element.style.outlineOffset = "1px";
''', element)

# set highlight setting to be removed after step
context.highlighted_element = {
'element': element
}

# Press Enter key
@step(u'Press Enter')
@done(u'Press Enter')
Expand Down Expand Up @@ -2378,10 +2421,15 @@ def addVariable(context, variable_name, result, encrypted=False):
env_variables[index]['variable_value'] = result
env_variables[index]['encrypted'] = encrypted
env_variables[index]['updated_by'] = context.PROXY_USER['user_id']
# make the request to cometa_django and add the environment variable
response = requests.patch('http://cometa_django:8000/api/variables/' + str(env_variables[index]['id']) + '/', headers={"Host": "cometa.local"}, json=env_variables[index])
if response.status_code == 200:
env_variables[index] = response.json()['data']

# do not update if scope is data-driven
if 'scope' in env_variables[index] and env_variables[index]['scope'] == 'data-driven':
logger.info("Will not send request to update the variable in co.meta since we are in 'data-driven' scope.")
else:
# make the request to cometa_django and add the environment variable
response = requests.patch('http://cometa_django:8000/api/variables/' + str(env_variables[index]['id']) + '/', headers={"Host": "cometa.local"}, json=env_variables[index])
if response.status_code == 200:
env_variables[index] = response.json()['data']
else: # create new variable
logger.debug("Creating variable")
# create data to send to django
Expand Down Expand Up @@ -2759,7 +2807,9 @@ def editFile(context, excelfile, value, cell):
sheet = wb.active

# assert the cell with value
cell_value = str(sheet[cell].value).strip()
cell = sheet[cell]
logger.debug(cell.data_type)
cell_value = str(cell.internal_value).strip()
value = str(value).strip()
logger.debug("Cell value: %s" % cell_value)
logger.debug("Value to compare: %s" % value)
Expand Down
5 changes: 4 additions & 1 deletion backend/behave/run_remote_from_django.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ ln -sf /opt/code/cometa_itself/environment.py ${FOLDERPATH}/environment.py

# execute the testcase
echo "1 - /usr/local/bin/behave $FEATURE_FILE --summary --junit --junit-directory ${FOLDERPATH}/junit_reports/"
/usr/local/bin/behave "$FEATURE_FILE" --summary --junit --junit-directory ${FOLDERPATH}/junit_reports/
/usr/local/bin/behave "$FEATURE_FILE" \
--summary --junit --junit-directory \
${FOLDERPATH}/junit_reports/ --no-skipped \
--quiet --no-multiline --format=null
8 changes: 8 additions & 0 deletions backend/src/backend/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ def get_queryset(self, request):
# Return all feature runs, including those marked as removed
return Feature_Runs.all_objects

class AdminDataDriven_run(admin.ModelAdmin):
model = DataDriven_Runs

def get_queryset(self, request):
# Return all feature runs, including those marked as removed
return DataDriven_Runs.all_objects

class AdminFeature_result(admin.ModelAdmin):
model = Feature_result
search_fields = ['feature_name', 'app_name', 'environment_name', 'department_name']
Expand Down Expand Up @@ -288,6 +295,7 @@ def fileExistsOnFS(self, obj):
admin.site.register(Folder, AdminFolder)
admin.site.register(Folder_Feature, AdminFolder_Feature)
admin.site.register(Feature_Runs, AdminFeature_run)
admin.site.register(DataDriven_Runs, AdminDataDriven_run)
admin.site.register(MiamiContact)
admin.site.register(Application)
admin.site.register(Environment)
Expand Down
29 changes: 28 additions & 1 deletion backend/src/backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,25 @@ def do_delete(self, *args, **kwargs):
# super(Feature_Runs, self).delete()

return True


class DataDriven_Runs(SoftDeletableModel):
run_id = models.AutoField(primary_key=True)
file = models.ForeignKey("File", on_delete=models.SET_NULL, null=True, related_name="ddr_file")
feature_results = models.ManyToManyField(Feature_result)
date_time = models.DateTimeField(default=datetime.datetime.utcnow, editable=True, null=False, blank=False)
archived = models.BooleanField(default=False)
status = models.CharField(max_length=100, default='')
total = models.IntegerField(default=0)
fails = models.IntegerField(default=0)
ok = models.IntegerField(default=0)
skipped = models.IntegerField(default=0)
execution_time = models.IntegerField(default=0)
pixel_diff = models.BigIntegerField(default=0)

class Meta:
ordering = ['-date_time']
verbose_name_plural = "Data Driven Runs"

class Feature_Task(models.Model):
task_id = models.AutoField(primary_key=True)
feature = models.ForeignKey(Feature, on_delete=models.CASCADE, related_name="feature_tasks")
Expand Down Expand Up @@ -1436,6 +1454,15 @@ def delete(self, using=None, soft=True, *args, **kwargs):

return super().delete(using=using, soft=soft, *args, **kwargs)

class FileData(SoftDeletableModel):
id = models.AutoField(primary_key=True)
file = models.ForeignKey(File, on_delete=models.CASCADE, related_name="file")
data = models.JSONField(default=dict)
extras = models.JSONField(default=dict)
created_on = models.DateTimeField(default=datetime.datetime.utcnow, editable=True, null=False, blank=False, help_text='When was created')

class Meta:
verbose_name_plural = "Files Data"

"""
Make sure to delete the files on the fs when ever deleting the record.
Expand Down
Loading

0 comments on commit 2caa770

Please sign in to comment.