diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 83ff2090..388d9fa7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -91,6 +91,7 @@ stages: # Build Selenoid browsers - echo -e "\e[0Ksection_start:`date +%s`:deploy_selenoid\r\e[0KRunning selenoid deployment script" - bash backend/selenoid/deploy_selenoid.sh + # - docker exec -it cometa_selenoid sh /etc/selenoid/check_for_custom_background.sh - echo -e "\e[0Ksection_end:`date +%s`:deploy_selenoid\r\e[0K" # Install new python packages in django and behave container - docker exec cometa_django poetry install diff --git a/backend/behave/behave_django/schedules/views.py b/backend/behave/behave_django/schedules/views.py index 3a3c8ac3..93d72086 100755 --- a/backend/behave/behave_django/schedules/views.py +++ b/backend/behave/behave_django/schedules/views.py @@ -156,8 +156,10 @@ def updated_step_actions(request): # Add your new created action files here actions_files = [ 'cometa_itself/steps/actions.py', + 'cometa_itself/steps/validation_actions.py', 'ee/cometa_itself/steps/rest_api.py', - 'ee/cometa_itself/steps/ai_actions.py' + 'ee/cometa_itself/steps/ai_actions.py', + 'ee/cometa_itself/steps/conditional_actions.py' ] # variable to contain action comment diff --git a/backend/behave/cometa_itself/environment.py b/backend/behave/cometa_itself/environment.py index 2c0dcf6a..9c794652 100755 --- a/backend/behave/cometa_itself/environment.py +++ b/backend/behave/cometa_itself/environment.py @@ -15,7 +15,6 @@ import os, pickle from selenium.common.exceptions import InvalidCookieDomainException - sys.path.append("/opt/code/behave_django") sys.path.append("/code/behave/cometa_itself/steps") @@ -25,6 +24,7 @@ from utility.encryption import * from utility.configurations import ConfigurationManager, load_configurations from modules.ai import AI +from tools.models import Condition LOGGER_FORMAT = "\33[96m[%(asctime)s][%(feature_id)s][%(current_step)s/%(total_steps)s][%(levelname)s][%(filename)s:%(lineno)d](%(funcName)s) -\33[0m %(message)s" @@ -417,8 +417,8 @@ def before_all(context): context.tempfiles = [ execution_data_file, ] - context.network_responses = [] + context.test_conditions_list: list[Condition] = [] # call update task to create a task with pid. task = { @@ -767,7 +767,8 @@ def after_all(context): @error_handling() def before_step(context, step): - + context.CURRENT_STEP = step + context.CURRENT_STEP_STATUS = "Failed" os.environ["current_step"] = str(context.counters["index"] + 1) # complete step name to let front know about the step that will be executed next step_name = "%s %s" % (step.keyword, step.name) @@ -939,6 +940,7 @@ def after_step(context, step): "step_result_info": step_result, "step_time": step.duration, "error": step_error, + "status": context.CURRENT_STEP_STATUS, "belongs_to": context.step_data["belongs_to"], "screenshots": json.dumps(screenshots), # load screenshots object "vulnerable_headers_count": vulnerable_headers_count, diff --git a/backend/behave/cometa_itself/steps/actions.py b/backend/behave/cometa_itself/steps/actions.py index b8086857..de2b1154 100755 --- a/backend/behave/cometa_itself/steps/actions.py +++ b/backend/behave/cometa_itself/steps/actions.py @@ -171,7 +171,7 @@ def step_impl(context,selector): def step_impl(context,css_selector): send_step_details(context, 'Looking for selector') elem = waitSelector(context, "css", css_selector) - send_step_details(context, 'Clicking') + send_step_details(context, 'Moving Mouse') ActionChains(context.browser).move_to_element(elem[0]).perform() # Moves the mouse to the center of css selector @@ -467,6 +467,7 @@ def test_folder(context, parameters = {}): # save to database save_message="I can test current IBM Cognos folder > Report: %s failed" % report_name + context.CURRENT_STEP_STATUS = "Failed" saveToDatabase(save_message, (time.time() - report_start_time) * 1000, 0, False, context ) # switch content @@ -497,6 +498,7 @@ def test_folder(context, parameters = {}): # logger.debug("Saveing report timing as step result to database") save_message="I can test current IBM Cognos folder > Report: %s tested" % report_name + context.CURRENT_STEP_STATUS = "Success" saveToDatabase(save_message, (time.time() - report_start_time) * 1000, 0, True, context ) # close the ibm cognos view @@ -606,11 +608,13 @@ def test_folder_aso(context, parameters = {}): # promptPage failed somehow logger.debug("Prompt Magic returned false - which means, we should fail this report [%s]." % report_name) logger.info("You might want to look at the screenshot or video and adjust timeouts or fix a broken report.") - logger.debug("Saveing report timing as step result to database.") + logger.debug("Saving report timing as step result to database.") + over_all_results_ok=False # save to database save_message="I can test current IBM Cognos folder > Report: %s failed" % report_name + context.CURRENT_STEP_STATUS = "Failed" saveToDatabase(save_message, (time.time() - report_start_time) * 1000, 0, False, context ) # switch content @@ -641,6 +645,7 @@ def test_folder_aso(context, parameters = {}): # logger.debug("Saveing report timing as step result to database") save_message="I can test current IBM Cognos folder > Report: %s tested" % report_name + context.CURRENT_STEP_STATUS = "Success" saveToDatabase(save_message, (time.time() - report_start_time) * 1000, 0, True, context ) # close the ibm cognos view @@ -3353,9 +3358,10 @@ def scrollThroughLazyLoading(context, xpath, MaxScrolls, MaxTimeOfLife): if __name__ != 'actions': sys.path.append('/code/behave/') from ee.cometa_itself.steps import rest_api - from ee.cometa_itself.steps import ai_actions - + from ee.cometa_itself.steps import ai_actions + from ee.cometa_itself.steps import conditional_actions + sys.path.append('/code/behave/cometa_itself') + from steps import validation_actions from steps import unimplemented_steps - \ No newline at end of file diff --git a/backend/behave/cometa_itself/steps/tools/common_functions.py b/backend/behave/cometa_itself/steps/tools/common_functions.py index 2c750fba..863afe10 100644 --- a/backend/behave/cometa_itself/steps/tools/common_functions.py +++ b/backend/behave/cometa_itself/steps/tools/common_functions.py @@ -56,6 +56,7 @@ from utility.functions import toWebP from utility.encryption import * +from tools.models import check_if_step_should_execute # setup logging logger = logging.getLogger("FeatureExecution") @@ -442,7 +443,17 @@ def execute(*args, **kwargs): # set page load timeout args[0].browser.set_page_load_timeout(step_timeout) # run the requested function - result = func(*args, **kwargs) + # Check if step should execute, It should not lies in the If else conditions + should_execute_the_step = check_if_step_should_execute(args[0]) + result = None + + if should_execute_the_step: + result = func(*args, **kwargs) + args[0].CURRENT_STEP_STATUS = "Success" + else: + args[0].CURRENT_STEP_STATUS = "Skipped" + logger.debug(f"######################### Skipping the step \" {args[0].CURRENT_STEP.name} \"#########################") + # if step executed without running into timeout cancel the timeout signal.alarm(0) # save the result to database @@ -452,6 +463,7 @@ def execute(*args, **kwargs): # return True meaning everything went as expected return result except Exception as err: + args[0].CURRENT_STEP_STATUS = "Failed" # reset timeout incase of exception in function signal.alarm(0) # print stack trace @@ -465,7 +477,7 @@ def execute(*args, **kwargs): (time.time() - start_time) * 1000, 0, False, - args[0], + args[0] ) except Exception as err: logger.error( @@ -516,6 +528,7 @@ def execute(*args, **kwargs): def saveToDatabase( step_name="", execution_time=0, pixel_diff=0, success=False, context=None ): + status = context.CURRENT_STEP_STATUS screenshots = os.environ["SCREENSHOTS"].split(".") compares = os.environ["COMPARES"].split(".") feature_id = context.feature_id @@ -526,7 +539,7 @@ def saveToDatabase( "execution_time": int(execution_time), "pixel_diff": float(pixel_diff), "success": success, - "status": "Success" if success else "Failed", + "status": status, "belongs_to": context.step_data["belongs_to"], "rest_api_id": context.step_data.get("rest_api", None), "notes": context.step_data.get("notes", {}), diff --git a/backend/behave/cometa_itself/steps/tools/models.py b/backend/behave/cometa_itself/steps/tools/models.py new file mode 100644 index 00000000..4319df7c --- /dev/null +++ b/backend/behave/cometa_itself/steps/tools/models.py @@ -0,0 +1,79 @@ +import logging + + +# setup logging +logger = logging.getLogger("FeatureExecution") + + +def is_step_conditional_statement(step): + step_name :str = step.name + conditional_steps = ("End If", "Else") + return step_name.startswith(conditional_steps) + +def check_if_step_should_execute(context): + step = context.CURRENT_STEP + should_execute_step = True + # Check if test flow is with in the condition + if len(context.test_conditions_list) > 0: + current_condition = context.test_conditions_list[-1] + logger.debug(f"Current condition values : {current_condition}") + # Check if current step is not conditional statement + if not is_step_conditional_statement(step): + logger.debug(f"Step {step.name} is with in the if condition") + + # When a condition is true and current step lies with in the If section then execute the current step + if current_condition.is_condition_true() and not current_condition.is_else_section_active(): + logger.debug(f"Step \"{step.name}\" will be executed with in the if section") + should_execute_step = True + # When a condition is false and current step lies with in the If section then skip the current step execution + elif not current_condition.is_condition_true() and not current_condition.is_else_section_active(): + logger.debug(f"Step \"{step.name}\" will be skipped with in the if section") + should_execute_step = False + + # When condition is false and current step lies with in the else section then execute the current step + elif not current_condition.is_condition_true() and current_condition.is_else_section_active(): + logger.debug(f"Step \"{step.name}\" will be executed with in the else section") + should_execute_step = True + # When condition is true and current step lies with in the else condition then skip the current step execution + elif current_condition.is_condition_true() and current_condition.is_else_section_active(): + logger.debug(f"Step \"{step.name}\" will be skipped with in the else section") + should_execute_step = False + else: + logger.debug("Executing step without any condition check") + should_execute_step = True + + return should_execute_step + +class Condition: + def __init__(self, index): + self.index = index + # if condition is True and not over if mean it is in the if section + # if condition is False and not over it mean it is in the else section + self._is_condition_true = True + self._is_else_section_active = False + self._is_condition_over = False + self._sub_conditions: list[Condition] = [] + + def set_condition(self, condition_result): + self._is_condition_true = condition_result + + def is_condition_true(self): + return self._is_condition_true + + def close_condition(self): + self._is_condition_over = True + + def is_flow_with_in_condition(self): + return self._is_condition_over + + def activate_else_section(self): + self._is_else_section_active = True + + def is_else_section_active(self): + return self._is_else_section_active + + def __str__(self): + return f"\n _is_condition_true : {self._is_condition_true}, \ + \n is_else_section_active: {self._is_else_section_active}, \ + \n is_condition_over: {self._is_condition_over}, \ + \n count_of_sub_conditions: {len(self._sub_conditions)}" \ No newline at end of file diff --git a/backend/behave/cometa_itself/steps/validation_actions.py b/backend/behave/cometa_itself/steps/validation_actions.py new file mode 100644 index 00000000..7631b839 --- /dev/null +++ b/backend/behave/cometa_itself/steps/validation_actions.py @@ -0,0 +1,78 @@ +from behave import * +import time +import sys +import os +import os.path +import logging +# import PIL +# Import utilities +sys.path.append(os.path.dirname(os.path.realpath(__file__))) +from tools.common import * +from tools.exceptions import * +from tools.variables import * + +# pycrypto imports +import base64 +base64.encodestring = base64.encodebytes +from hashlib import md5 +from pathlib import Path +from tools import expected_conditions as CEC +import sys + +sys.path.append("/opt/code/behave_django") +sys.path.append('/code/behave/cometa_itself/steps') + +from utility.functions import * +from utility.configurations import ConfigurationManager +from utility.common import * +from utility.encryption import * +from tools.common_functions import * + +SCREENSHOT_PREFIX = ConfigurationManager.get_configuration('COMETA_SCREENSHOT_PREFIX', '') +ENCRYPTION_START = ConfigurationManager.get_configuration('COMETA_ENCRYPTION_START', '') + +# setup logging +logger = logging.getLogger('FeatureExecution') + + +@step(u'Validate if "{selector}" present in the browser in "{time}" seconds and save result in "{variable}"') +@done(u'Validate if "{selector}" present in the browser in "{time}" seconds and save result in "{variable}"') +def validate_element_presence(context, selector, time, variable): + send_step_details(context, f"Validating if selector '{selector}' is present within {time} seconds") + + try: + # Use waitSelector to get the element + element = waitSelector(context, "css", selector, max_timeout=int(time)) + if type(element) == list: + element = element[0] + + result = element is not None # If element is returned, it's present + send_step_details(context, f"Selector '{selector}' is present: {result}") + except Exception as e: + result = False # If an exception occurs, the element is not present + logger.debug(f"Exception while checking presence of '{selector}': {e}") + + # Save the result to the variable + addTestRuntimeVariable(context, variable, str(result)) + send_step_details(context, f"Presence validation result for '{selector}' saved to variable '{variable}': {result}") + + +@step(u'Validate if "{selector}" appeared in the browser in "{time}" seconds and save result in "{variable}"') +@done(u'Validate if "{selector}" appeared in the browser in "{time}" seconds and save result in "{variable}"') +def validate_element_visibility(context, selector, time, variable): + send_step_details(context, f"Validating if selector '{selector}' appeared within {time} seconds") + + try: + # Use waitSelector to get the element + element = waitSelector(context, "css", selector, max_timeout=int(time)) + if type(element) == list: + element = element[0] + result = element.is_displayed() # Check if the element is visible + send_step_details(context, f"Selector '{selector}' appeared: {result}") + except Exception as e: + result = False # If an exception occurs, the element is not visible + logger.debug(f"Exception while checking visibility of '{selector}': {e}") + + # Save the result to the variable + addTestRuntimeVariable(context, variable, str(result)) + send_step_details(context, f"Visibility validation result for '{selector}' saved to variable '{variable}': {result}") diff --git a/backend/behave/ee/cometa_itself/steps/conditional_actions.py b/backend/behave/ee/cometa_itself/steps/conditional_actions.py new file mode 100644 index 00000000..b110de2b --- /dev/null +++ b/backend/behave/ee/cometa_itself/steps/conditional_actions.py @@ -0,0 +1,66 @@ +# ### +# Sponsored by Mercedes-Benz AG, Stuttgart +# ### + +from behave import ( + step, + use_step_matcher +) +import sys, requests, re, json +sys.path.append('/code/behave/cometa_itself/steps') +from actions import ( + done, + logger, + addVariable +) +from tools.exceptions import CustomError +from tools.models import Condition + +use_step_matcher("re") + +@step(u'If "(?P.+?)" "(?P.+?)" "(?P.+?)"') +@done(u'If "{value1}" "{condition}" "{value2}"') +def start_if(context, value1, condition, value2): + condition_result = False + if condition == 'equals': + condition_result = value1==value2 + if condition == 'not equals': + condition_result = value1!=value2 + elif condition == 'contains': + condition_result = value1.find(value2)>=0 + elif condition == 'not contains': + condition_result = value1.find(value2)>=0 + elif condition == '>=': + condition_result = float(value1)>=float(value2) + elif condition == '<=': + condition_result = float(value1)<=float(value2) + elif condition == '==': + condition_result = float(value1)==float(value2) + elif condition == '!=': + condition_result = float(value1)!=float(value2) + + condition = Condition(index=len(context.test_conditions_list)) + condition.set_condition(condition_result) + context.test_conditions_list.append(condition) + + +@step(u'Else') +@done(u'Else') +def start_else(context): + if len(context.test_conditions_list)==0: + raise CustomError("Flow is not with in the If Condition") + + context.test_conditions_list[-1].activate_else_section() + + +@step(u'End If') +@done(u'End If') +def end_if(context): + if len(context.test_conditions_list)==0: + raise CustomError("Flow is not with in the If Condition") + + last_condition = context.test_conditions_list.pop() + last_condition.close_condition() + + +use_step_matcher("parse") \ No newline at end of file diff --git a/backend/images/Cometa_Black_Background.jpg b/backend/images/Cometa_Black_Background.jpg new file mode 100644 index 00000000..8ed981be Binary files /dev/null and b/backend/images/Cometa_Black_Background.jpg differ diff --git a/backend/images/Cometa_Colored_Background.jpg b/backend/images/Cometa_Colored_Background.jpg new file mode 100644 index 00000000..c38e489f Binary files /dev/null and b/backend/images/Cometa_Colored_Background.jpg differ diff --git a/backend/selenoid/amvara.png b/backend/selenoid/amvara.png deleted file mode 100644 index de9e0c49..00000000 Binary files a/backend/selenoid/amvara.png and /dev/null differ diff --git a/backend/selenoid/background.png b/backend/selenoid/background.png new file mode 100644 index 00000000..8ed981be Binary files /dev/null and b/backend/selenoid/background.png differ diff --git a/backend/selenoid/check_for_custom_background.sh b/backend/selenoid/check_for_custom_background.sh new file mode 100755 index 00000000..06db9f20 --- /dev/null +++ b/backend/selenoid/check_for_custom_background.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# Check if the file exists at the source location +SOURCE_FILE="/code/config/images/fluxbox/background.png" +DESTINATION_PATH="/etc/selenoid/" + +mkdir -p $DESTINATION_PATH + +DESTINATION_FILE="/etc/selenoid/background.png" + +if [ -f "$SOURCE_FILE" ]; then + echo "File exists at $SOURCE_FILE. Copying to $DESTINATION_FILE..." + cp -rf "$SOURCE_FILE" "$DESTINATION_FILE" +else + echo "Custom background file does not exist at $SOURCE_FILE, Skipping copy. If custom browser background needed can be set using cometa backend" +fi diff --git a/backend/selenoid/deploy_selenoid.sh b/backend/selenoid/deploy_selenoid.sh index d2531cab..9603145d 100755 --- a/backend/selenoid/deploy_selenoid.sh +++ b/backend/selenoid/deploy_selenoid.sh @@ -44,7 +44,7 @@ pwd=`pwd` if [ -f "$customBg" ]; then bgImage=$customBg else - bgImage="${pwd}/amvara.png" + bgImage="${pwd}/background.png" fi fluxboxPath=${pwd}/.fluxbox diff --git a/backend/selenoid/start.sh b/backend/selenoid/start.sh new file mode 100755 index 00000000..29700724 --- /dev/null +++ b/backend/selenoid/start.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# Debugging: Check if the file exists + +/etc/selenoid/check_for_custom_background.sh + +# entrypoint with limit on browser executions to CPU-2 or Defaulting to 2 if number is less then 2 +# Calculate the limit based on the number of available processors +LIMIT=$(( $(nproc) - 2 )) +LIMIT=$(( LIMIT > 2 ? LIMIT : 2 )) + +# Start Selenoid with the calculated limit and provided configurations +/usr/bin/selenoid \ + -listen :4444 \ + -conf /etc/selenoid/browsers.json \ + -video-output-dir /opt/selenoid/video \ + -log-output-dir /opt/selenoid/logs \ + -container-network cometa_testing \ + -limit $LIMIT diff --git a/backend/src/defaults/configurationfile.json b/backend/src/defaults/configurationfile.json new file mode 100644 index 00000000..69c3c709 --- /dev/null +++ b/backend/src/defaults/configurationfile.json @@ -0,0 +1,11 @@ +[ + { + "model": "configuration.ConfigurationFile", + "pk": 1, + "fields": { + "name": "TEST_ENV_BACKGROUND_IMAGE", + "file_name": "background.png", + "path": "images/fluxbox" + } + } +] \ No newline at end of file diff --git a/backend/src/modules/configuration/admin.py b/backend/src/modules/configuration/admin.py index 26e7a687..c731ba8e 100644 --- a/backend/src/modules/configuration/admin.py +++ b/backend/src/modules/configuration/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from .models import * - +@admin.register(Configuration) class ConfigurationAdmin(admin.ModelAdmin): model = Configuration search_fields = ['id','configuration_name'] @@ -9,4 +9,9 @@ class ConfigurationAdmin(admin.ModelAdmin): list_filter = ('encrypted',) readonly_fields = ('created_by', 'updated_by','created_on','updated_on') -admin.site.register(Configuration, ConfigurationAdmin) + +@admin.register(ConfigurationFile) +class ConfigurationFilesAdmin(admin.ModelAdmin): + list_display = ('name', 'file_name','path') # Columns displayed in the admin list view + list_filter = ('path',) # Enable filtering by the 'path' field + search_fields = ('name', 'file_name','path') # Enable searching by 'name' and 'path' diff --git a/backend/src/modules/configuration/models.py b/backend/src/modules/configuration/models.py index 5f90a03d..57545b04 100644 --- a/backend/src/modules/configuration/models.py +++ b/backend/src/modules/configuration/models.py @@ -4,9 +4,12 @@ from django.db import models from backend.models import OIDCAccount from django.core.exceptions import ValidationError -import datetime +import datetime, os from backend.utility.encryption import encrypt from backend.utility.configurations import ConfigurationManager + + + class Configuration(models.Model): id = models.AutoField(primary_key=True) configuration_name = models.CharField(max_length=100, default=None, blank=False, null=False, unique=True) @@ -38,4 +41,58 @@ class Meta: verbose_name_plural = "Configurations" def __str__(self) -> str: - return self.configuration_name \ No newline at end of file + return self.configuration_name + +# Define the parent directory for file uploads +PARENT_DIR = '/code/config/' + +def validate_safe_path(value): + """ + Validates the given path to ensure it resides within the allowed parent directory. + """ + full_path = os.path.abspath(os.path.join(PARENT_DIR, value.strip('/'))) + if not full_path.startswith(PARENT_DIR): + raise ValidationError("Invalid path: Detected path traversal attempt.") + return full_path + +def validate_file_size(file): + """Validates that the file size does not exceed 5 MB.""" + max_size_mb = 5 + if file.size > max_size_mb * 1024 * 1024: + raise ValidationError(f"File size cannot exceed {max_size_mb} MB.") + +class ConfigurationFile(models.Model): + name = models.CharField(max_length=100) # Name field + file_name = models.CharField(max_length=50,blank=False, default="") # Name field + path = models.CharField( + max_length=150, + help_text="Specify the relative path under '/code/config/'.", + ) # Path field + file = models.FileField(upload_to="config", blank=True, null=True) # Optional file field + + def __str__(self): + return self.name + + def save(self, *args, **kwargs): + """ + Override the save method to automatically save the file to the given path. + """ + if self.file: + # Validate file size + validate_file_size(self.file) + + # Validate and construct the full save path + sanitized_path = validate_safe_path(self.path) + os.makedirs(sanitized_path, exist_ok=True) # Ensure the directory exists + + # Save the file to the sanitized path + file_path = os.path.join(sanitized_path, self.file_name) + with open(file_path, 'wb') as destination: + for chunk in self.file.chunks(): + destination.write(chunk) + # Update the file field with the new file path (relative to MEDIA_ROOT) + self.file = None + + # Call the original save method + super().save(*args, **kwargs) + diff --git a/backend/ws-server/app/index.js b/backend/ws-server/app/index.js index c665f2e6..68fb79dc 100644 --- a/backend/ws-server/app/index.js +++ b/backend/ws-server/app/index.js @@ -255,10 +255,12 @@ app.post('/feature/:feature_id/stepFinished', (req, res) => { datetime: req.body.datetime, step_time: req.body.step_time, error: req.body.error, + status: req.body.status, user_id: +req.body.user_id, screenshots: req.body.screenshots ? JSON.parse(req.body.screenshots) : {}, vulnerable_headers_count:req.body.vulnerable_headers_count } + io.emit('message', payload) // Add message to history const messages = constructRun(+req.params.feature_id, +req.body.run_id) diff --git a/docker-compose-debug.yml b/docker-compose-debug.yml index f8622ad4..8b649d8f 100644 --- a/docker-compose-debug.yml +++ b/docker-compose-debug.yml @@ -22,6 +22,7 @@ services: - ./data/cometa/videos:/code/behave/videos - ./backend:/code - ./backend/src:/opt/code:rw + - ./data/cometa/config:/code/config:rw - "./backend/crontabs/cometa_django_crontab:/etc/cron.d/crontab" working_dir: /opt/code environment: @@ -52,6 +53,7 @@ services: - ./data/cometa/videos:/opt/code/videos - ./backend/behave:/opt/code:rw - ./backend:/code + - ./data/cometa/config:/code/config:rw - ./data/redis/certs:/share/certs - "./backend/behave/schedules/crontab:/etc/cron.d/crontab" # command: "/opt/code/entry.sh -d" @@ -91,7 +93,8 @@ services: - 4444:4444 volumes: - /var/run/docker.sock:/var/run/docker.sock - - ./backend/selenoid/:/etc/selenoid/:ro + - ./backend/selenoid/:/etc/selenoid/:rw + - ./data/cometa/config/images:/code/config/images:rw - ./data/cometa/videos:/opt/selenoid/video environment: TZ: Europe/Berlin @@ -99,14 +102,7 @@ services: networks: - testing restart: always - # entrypoint with limit on browser executions to CPU-2 or Defaulting to 2 if number is less then 2 - entrypoint: sh -c "LIMIT=$$(($$(nproc)-2)) && LIMIT=$$((LIMIT > 2 ? $$LIMIT:2)) && /usr/bin/selenoid \ - -listen :4444 \ - -conf /etc/selenoid/browsers.json \ - -video-output-dir /opt/selenoid/video \ - -log-output-dir /opt/selenoid/logs \ - -container-network cometa_testing \ - -limit $$LIMIT" + entrypoint: sh /etc/selenoid/start.sh novnc: image: cometa/novnc:1.0 diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index e53cf19e..27762cea 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -20,6 +20,7 @@ services: volumes: - ./data/cometa/screenshots:/code/behave/screenshots - ./data/cometa/videos:/code/behave/videos + - ./data/cometa/config:/code/config:rw - ./backend:/code - ./backend/src:/opt/code:rw - "./backend/crontabs/cometa_django_crontab:/etc/cron.d/crontab" @@ -50,6 +51,7 @@ services: volumes: - ./data/cometa/screenshots:/opt/code/screenshots - ./data/cometa/videos:/opt/code/videos + - ./data/cometa/config:/code/config:rw - ./backend/behave:/opt/code:rw - ./backend:/code - ./data/redis/certs:/share/certs @@ -90,7 +92,8 @@ services: - 4444:4444 volumes: - /var/run/docker.sock:/var/run/docker.sock - - ./backend/selenoid/:/etc/selenoid/:ro + - ./backend/selenoid/:/etc/selenoid/:rw + - ./data/cometa/config/images:/code/config/images:rw - ./data/cometa/videos:/opt/selenoid/video environment: TZ: Europe/Berlin @@ -98,14 +101,7 @@ services: networks: - testing restart: always - # entrypoint with limit on browser executions to CPU-2 or Defaulting to 2 if number is less then 2 - entrypoint: sh -c "LIMIT=$$(($$(nproc)-2)) && LIMIT=$$((LIMIT > 2 ? $$LIMIT:2)) && /usr/bin/selenoid \ - -listen :4444 \ - -conf /etc/selenoid/browsers.json \ - -video-output-dir /opt/selenoid/video \ - -log-output-dir /opt/selenoid/logs \ - -container-network cometa_testing \ - -limit $$LIMIT" + entrypoint: sh /etc/selenoid/start.sh novnc: image: cometa/novnc:1.0 diff --git a/docker-compose.yml b/docker-compose.yml index a1960b8a..8b840292 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,10 +18,12 @@ services: driver: json-file command: bash start.sh volumes: - - /data/cometa/screenshots:/code/behave/screenshots - - /data/cometa/videos:/code/behave/videos - - /data/cometa/pdf:/code/behave/pdf - - /data/cometa/downloads:/code/behave/downloads + - /var/run/docker.sock:/var/run/docker.sock + - ./data/cometa/screenshots:/code/behave/screenshots + - ./data/cometa/videos:/code/behave/videos + - ./data/cometa/pdf:/code/behave/pdf + - ./data/cometa/downloads:/code/behave/downloads + - ./data/cometa/config:/code/config:rw - ./backend:/code - ./backend/src:/opt/code:rw - "./backend/crontabs/cometa_django_crontab:/etc/cron.d/crontab" @@ -49,11 +51,13 @@ services: redis: condition: service_healthy volumes: - - /data/cometa/screenshots:/opt/code/screenshots - - /data/cometa/videos:/opt/code/videos - - /data/cometa/pdf:/code/behave/pdf - - /data/cometa/downloads:/code/behave/downloads - - /data/redis/certs:/share/certs + - /var/run/docker.sock:/var/run/docker.sock + - ./data/cometa/screenshots:/opt/code/screenshots + - ./data/cometa/videos:/opt/code/videos + - ./data/cometa/pdf:/code/behave/pdf + - ./data/cometa/downloads:/code/behave/downloads + - ./data/redis/certs:/share/certs + - ./data/cometa/config:/code/config:rw - ./backend/behave:/opt/code:rw - ./backend:/code - "./backend/behave/schedules/crontab:/etc/cron.d/crontab" @@ -93,22 +97,16 @@ services: - "5532" volumes: - /var/run/docker.sock:/var/run/docker.sock - - ./backend/selenoid/:/etc/selenoid/:ro - - /data/cometa/videos:/opt/selenoid/video + - ./backend/selenoid/:/etc/selenoid/:rw + - ./data/cometa/config/images:/code/config/images:rw + - ./data/cometa/videos:/opt/selenoid/video environment: TZ: Europe/Berlin OVERRIDE_VIDEO_OUTPUT_DIR: ${PWD}/data/cometa/videos networks: - testing restart: always - # entrypoint with limit on browser executions to CPU-2 or Defaulting to 2 if number is less then 2 - entrypoint: sh -c "LIMIT=$$(($$(nproc)-2)) && LIMIT=$$((LIMIT > 2 ? $$LIMIT:2)) && /usr/bin/selenoid \ - -listen :4444 \ - -conf /etc/selenoid/browsers.json \ - -video-output-dir /opt/selenoid/video \ - -log-output-dir /opt/selenoid/logs \ - -container-network cometa_testing \ - -limit $$LIMIT" + entrypoint: sh /etc/selenoid/start.sh novnc: image: cometa/novnc:1.0 @@ -140,7 +138,7 @@ services: - ./front/apache-conf/openidc.conf_:/usr/local/apache2/conf/openidc.conf - ./front/apache-conf/paths.conf:/usr/local/apache2/conf/paths.conf - ./front/apache-conf/mod_auth_openidc.so:/usr/local/apache2/modules/mod_auth_openidc.so - - /data/cometa/screenshots:/screenshots + - ./data/cometa/screenshots:/screenshots working_dir: /code/front privileged: true ports: diff --git a/front/src/app/views/step-view/step-view.component.html b/front/src/app/views/step-view/step-view.component.html index 73c82f0b..f7b997f2 100755 --- a/front/src/app/views/step-view/step-view.component.html +++ b/front/src/app/views/step-view/step-view.component.html @@ -98,7 +98,7 @@
@@ -136,11 +136,11 @@
- {{ passed ? 'Passed' : 'Failed' }} + > + {{ item.status=='Success'?'Passed':item.status }}
diff --git a/front/src/app/views/step-view/step-view.component.scss b/front/src/app/views/step-view/step-view.component.scss index 8823578b..2db28a83 100755 --- a/front/src/app/views/step-view/step-view.component.scss +++ b/front/src/app/views/step-view/step-view.component.scss @@ -330,12 +330,15 @@ flex: 0 5px; max-width: 5px; height: 100%; - &.success { + &.success, &.Success { background-color: $good; } - &.failed { + &.failed, &.Failed { background-color: $bad; } + &.skipped, &.Skipped { + background-color: rgb(204, 204, 129); + } @include for-tablet-portrait-up { display: none; } @@ -446,3 +449,13 @@ // } } } + +.success { + background-color: $good; +} +.failed { + background-color: $bad; +} +.skipped { + background-color: rgb(182, 182, 101); +} \ No newline at end of file diff --git a/front/src/assets/config.json b/front/src/assets/config.json index 2c9ae385..0d52d6a8 100755 --- a/front/src/assets/config.json +++ b/front/src/assets/config.json @@ -1,5 +1,5 @@ { - "version": "3.0.3", + "version": "3.0.5", "language": "en", "appTitle": "co.meta", "scenario": "dev", @@ -232,6 +232,26 @@ "{Code is Poetry}" ], "changelog": [ + { + "version": "3.0.5", + "date": "2024-11-21", + "features": [ + { + "title": "#5414 Change background image of test environment ", + "description": "Backgroud images can be change for the test browser and mobile device using Cometa admin section" + } + ] + }, + { + "version": "3.0.4", + "date": "2024-11-20", + "features": [ + { + "title": "#5414 - Conditional statements ", + "description": "Implemented conditional steps (if...else...end if) with conditions such as equals, not equals, contains, not contains, and for numeric values (<=, >=, ==, !=). Also implemented steps to check visibility and presence of an element." + } + ] + }, { "version": "3.0.3", "date": "2024-11-13",