From a75cdc692e8ab1c1a66626bf4667abbab3a9d399 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 18:51:38 +0100 Subject: [PATCH 01/18] added draft for pipeline --- .github/workflows/test-automation.yml | 93 +++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .github/workflows/test-automation.yml diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml new file mode 100644 index 0000000..31fc070 --- /dev/null +++ b/.github/workflows/test-automation.yml @@ -0,0 +1,93 @@ +name: Test Automation + +on: + push: + branches: [ main, master, deployment/pipeline] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install Chrome + run: | + wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/sources.list.d/google.list' + sudo apt-get update + sudo apt-get install google-chrome-stable + + - name: Install ChromeDriver + run: | + CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) + CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") + curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" + unzip chromedriver.zip + chmod +x chromedriver + sudo mv chromedriver /usr/local/bin/ + + - name: Install Allure + run: | + curl -o allure-2.24.1.tgz -OLs https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/2.24.1/allure-commandline-2.24.1.tgz + sudo tar -zxvf allure-2.24.1.tgz -C /opt/ + sudo ln -s /opt/allure-2.24.1/bin/allure /usr/bin/allure + allure --version + + - name: Cache Python dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run UI tests with Allure + run: | + behave -f allure_behave.formatter:AllureFormatter -o allure-results ./features --tags=@ui + continue-on-error: true + + - name: Run API tests with Allure + run: | + behave -f allure_behave.formatter:AllureFormatter -o allure-results ./features --tags=@api + continue-on-error: true + + - name: Generate Allure Report + if: always() + run: | + allure generate allure-results --clean -o allure-report + + - name: Upload Allure Report + uses: actions/upload-artifact@v3 + if: always() + with: + name: allure-report + path: allure-report + + - name: Upload Screenshots + uses: actions/upload-artifact@v3 + if: failure() + with: + name: failure-screenshots + path: test_reports/screenshots/ + + - name: Deploy Allure Report to GitHub Pages + if: always() + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./allure-report + publish_branch: gh-pages \ No newline at end of file From 6c25e9ffac3ad3c7921fc1a0581ad5bcdedce56a Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 18:53:54 +0100 Subject: [PATCH 02/18] fixed version --- .github/workflows/test-automation.yml | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index 31fc070..edbbebd 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -12,28 +12,28 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.12' - - name: Install Chrome - run: | - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/sources.list.d/google.list' - sudo apt-get update - sudo apt-get install google-chrome-stable - - - name: Install ChromeDriver - run: | - CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) - CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") - curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" - unzip chromedriver.zip - chmod +x chromedriver - sudo mv chromedriver /usr/local/bin/ +# - name: Install Chrome +# run: | +# wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - +# sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/sources.list.d/google.list' +# sudo apt-get update +# sudo apt-get install google-chrome-stable +# +# - name: Install ChromeDriver +# run: | +# CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) +# CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") +# curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" +# unzip chromedriver.zip +# chmod +x chromedriver +# sudo mv chromedriver /usr/local/bin/ - name: Install Allure run: | @@ -71,14 +71,14 @@ jobs: allure generate allure-results --clean -o allure-report - name: Upload Allure Report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: allure-report path: allure-report - name: Upload Screenshots - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: failure-screenshots From 7bc526db9977aa6266f58fd6e10542a6bedf135d Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 18:57:25 +0100 Subject: [PATCH 03/18] fixed start command --- .github/workflows/test-automation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index edbbebd..04143bf 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -57,12 +57,12 @@ jobs: - name: Run UI tests with Allure run: | - behave -f allure_behave.formatter:AllureFormatter -o allure-results ./features --tags=@ui + behave --tags=@ui continue-on-error: true - name: Run API tests with Allure run: | - behave -f allure_behave.formatter:AllureFormatter -o allure-results ./features --tags=@api + behave --tags=@api continue-on-error: true - name: Generate Allure Report From 32a532aa8c2dbbded5ffc216aaab087ace3299d6 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:04:24 +0100 Subject: [PATCH 04/18] fixed environment to run it on ubuntu, try 1 of ... --- .github/workflows/test-automation.yml | 30 ++++---- features/api_tests.feature | 2 +- features/environment.py | 104 +++++++++++++++++++++++++- 3 files changed, 118 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index 04143bf..5309680 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -19,21 +19,21 @@ jobs: with: python-version: '3.12' -# - name: Install Chrome -# run: | -# wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - -# sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/sources.list.d/google.list' -# sudo apt-get update -# sudo apt-get install google-chrome-stable -# -# - name: Install ChromeDriver -# run: | -# CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) -# CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") -# curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" -# unzip chromedriver.zip -# chmod +x chromedriver -# sudo mv chromedriver /usr/local/bin/ + - name: Install Chrome + run: | + wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - + sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/sources.list.d/google.list' + sudo apt-get update + sudo apt-get install google-chrome-stable + + - name: Install ChromeDriver + run: | + CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) + CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") + curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" + unzip chromedriver.zip + chmod +x chromedriver + sudo mv chromedriver /usr/local/bin/ - name: Install Allure run: | diff --git a/features/api_tests.feature b/features/api_tests.feature index e54db31..a40c45f 100644 --- a/features/api_tests.feature +++ b/features/api_tests.feature @@ -76,7 +76,7 @@ Feature: ReqRes API Users Endpoint And the response should contain "error" field #last AC I'm already tired but still enthusiastic - @delayed + @delayed Scenario Outline: Get users list with different delay times When I send GET request to "users" with delay of seconds Then the response status code should be 200 diff --git a/features/environment.py b/features/environment.py index d3ee506..e3daa30 100644 --- a/features/environment.py +++ b/features/environment.py @@ -1,4 +1,13 @@ from datetime import datetime +import allure +from allure_commons.types import AttachmentType +from behave.model_core import Status +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.chrome.options import Options +import os +import shutil +import tempfile from behave.parser import parse_file @@ -7,9 +16,22 @@ from config.users import Users from behave.model import Scenario from behave.model import Table -import os +def create_chrome_options(): + chrome_options = Options() + # Add required options + chrome_options.add_argument('--no-sandbox') + chrome_options.add_argument('--headless') # Run in headless mode for CI + chrome_options.add_argument('--disable-dev-shm-usage') + chrome_options.add_argument('--disable-gpu') + + # Create a temporary directory for user data + user_data_dir = tempfile.mkdtemp() + chrome_options.add_argument(f'--user-data-dir={user_data_dir}') + + return chrome_options, user_data_dir + def before_all(context): # Load configuration context.config = { @@ -39,6 +61,14 @@ def before_all(context): with open(feature_path, "w", encoding="utf-8") as file: file.write(updated_content) + # Create reports and screenshots directories + for dir_path in ['test_reports', 'test_reports/screenshots']: + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + # Store the user data directory path for cleanup + context.user_data_dir = None + def before_scenario(context, scenario): # Initialize WebDriver only for scenarios tagged with @ui @@ -72,6 +102,39 @@ def before_feature(context, feature): parsed_feature = parse_file(feature_file_path) feature.__dict__.update(parsed_feature.__dict__) # Force update in place + if 'api' in feature.tags: + # Skip browser setup for API features + return + + # Set up Chrome options and user data directory + chrome_options, user_data_dir = create_chrome_options() + context.user_data_dir = user_data_dir + + # Initialize the WebDriver + service = Service() + context.driver = webdriver.Chrome(service=service, options=chrome_options) + context.driver.implicitly_wait(10) + + # Process scenario outlines + for scenario in feature.walk_scenarios(): + if hasattr(scenario, 'examples'): + for example in scenario.examples: + if '' in str(example.table): + new_rows = [] + for user in context.valid_users: + new_rows.append([user]) + example.table.rows = new_rows + +def after_feature(context, feature): + if hasattr(context, 'driver'): + context.driver.quit() + + # Clean up the user data directory + if context.user_data_dir and os.path.exists(context.user_data_dir): + try: + shutil.rmtree(context.user_data_dir) + except Exception as e: + print(f"Failed to remove user data directory: {e}") def after_all(context): # Optionally rename the report with timestamp @@ -80,4 +143,41 @@ def after_all(context): os.rename( 'test_reports/report.html', f'test_reports/report_{timestamp}.html' - ) \ No newline at end of file + ) + + # Clean up any remaining user data directories + if hasattr(context, 'user_data_dir') and context.user_data_dir: + try: + shutil.rmtree(context.user_data_dir) + except Exception as e: + print(f"Failed to remove user data directory: {e}") + +def after_step(context, step): + if step.status == Status.failed and hasattr(context, 'driver'): + # Get scenario and step names + scenario_name = ''.join(e for e in context.scenario.name if e.isalnum() or e == '_') + step_name = ''.join(e for e in step.name if e.isalnum() or e == '_') + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + + try: + # Take screenshot + screenshot_name = f"{scenario_name}_{step_name}_{timestamp}.png" + screenshot_path = os.path.join('test_reports/screenshots', screenshot_name) + context.driver.save_screenshot(screenshot_path) + + # Attach screenshot to Allure report + allure.attach( + context.driver.get_screenshot_as_png(), + name="Screenshot", + attachment_type=AttachmentType.PNG + ) + + # Attach page source to Allure report + allure.attach( + context.driver.page_source, + name="Page Source", + attachment_type=AttachmentType.HTML + ) + + except Exception as e: + print(f"Failed to take screenshot: {str(e)}") \ No newline at end of file From b23dba5c0ae808adc94a974940040e8dd29814d4 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:09:55 +0100 Subject: [PATCH 05/18] fixed environment to run it on ubuntu, try 2 of ... --- .github/workflows/test-automation.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index 5309680..5820019 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -21,10 +21,11 @@ jobs: - name: Install Chrome run: | - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/sources.list.d/google.list' sudo apt-get update - sudo apt-get install google-chrome-stable + sudo apt-get install -y wget + wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo apt-get install -y ./google-chrome-stable_current_amd64.deb + google-chrome --version - name: Install ChromeDriver run: | @@ -34,6 +35,7 @@ jobs: unzip chromedriver.zip chmod +x chromedriver sudo mv chromedriver /usr/local/bin/ + chromedriver --version - name: Install Allure run: | From f94a0d61d56dcede30ccfe7532289164a127bbdc Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:11:21 +0100 Subject: [PATCH 06/18] fixed environment to run it on ubuntu, try 3 of ... --- .github/workflows/test-automation.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index 5820019..0826d85 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -27,15 +27,15 @@ jobs: sudo apt-get install -y ./google-chrome-stable_current_amd64.deb google-chrome --version - - name: Install ChromeDriver - run: | - CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) - CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") - curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" - unzip chromedriver.zip - chmod +x chromedriver - sudo mv chromedriver /usr/local/bin/ - chromedriver --version +# - name: Install ChromeDriver +# run: | +# CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) +# CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") +# curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" +# unzip chromedriver.zip +# chmod +x chromedriver +# sudo mv chromedriver /usr/local/bin/ +# chromedriver --version - name: Install Allure run: | From e3cf4efb20351983bb6550b374e7ba6148cb1da7 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:14:24 +0100 Subject: [PATCH 07/18] fixed environment to run it on ubuntu, try 3 of ... --- features/environment.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/features/environment.py b/features/environment.py index e3daa30..ae061b1 100644 --- a/features/environment.py +++ b/features/environment.py @@ -27,10 +27,10 @@ def create_chrome_options(): chrome_options.add_argument('--disable-gpu') # Create a temporary directory for user data - user_data_dir = tempfile.mkdtemp() - chrome_options.add_argument(f'--user-data-dir={user_data_dir}') + # user_data_dir = tempfile.mkdtemp() + # chrome_options.add_argument(f'--user-data-dir={user_data_dir}') - return chrome_options, user_data_dir + return chrome_options, #user_data_dir def before_all(context): # Load configuration @@ -107,8 +107,8 @@ def before_feature(context, feature): return # Set up Chrome options and user data directory - chrome_options, user_data_dir = create_chrome_options() - context.user_data_dir = user_data_dir + chrome_options = create_chrome_options() + # context.user_data_dir = user_data_dir # Initialize the WebDriver service = Service() From 44082833d6a931c64c050626d1000aa10663421f Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:19:52 +0100 Subject: [PATCH 08/18] fixed environment to run it on ubuntu, try 4 of ... --- features/environment.py | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/features/environment.py b/features/environment.py index ae061b1..5b02e41 100644 --- a/features/environment.py +++ b/features/environment.py @@ -25,12 +25,7 @@ def create_chrome_options(): chrome_options.add_argument('--headless') # Run in headless mode for CI chrome_options.add_argument('--disable-dev-shm-usage') chrome_options.add_argument('--disable-gpu') - - # Create a temporary directory for user data - # user_data_dir = tempfile.mkdtemp() - # chrome_options.add_argument(f'--user-data-dir={user_data_dir}') - - return chrome_options, #user_data_dir + return chrome_options def before_all(context): # Load configuration @@ -115,15 +110,6 @@ def before_feature(context, feature): context.driver = webdriver.Chrome(service=service, options=chrome_options) context.driver.implicitly_wait(10) - # Process scenario outlines - for scenario in feature.walk_scenarios(): - if hasattr(scenario, 'examples'): - for example in scenario.examples: - if '' in str(example.table): - new_rows = [] - for user in context.valid_users: - new_rows.append([user]) - example.table.rows = new_rows def after_feature(context, feature): if hasattr(context, 'driver'): From c22564070c45038e77cdd5a76b16546e421babf3 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:23:44 +0100 Subject: [PATCH 09/18] pipeline v2 --- .github/workflows/test-automation.yml | 113 ++++++++------------------ 1 file changed, 32 insertions(+), 81 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index 0826d85..c6884f6 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -12,84 +12,35 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.12' - - - name: Install Chrome - run: | - sudo apt-get update - sudo apt-get install -y wget - wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo apt-get install -y ./google-chrome-stable_current_amd64.deb - google-chrome --version - -# - name: Install ChromeDriver -# run: | -# CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) -# CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") -# curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" -# unzip chromedriver.zip -# chmod +x chromedriver -# sudo mv chromedriver /usr/local/bin/ -# chromedriver --version - - - name: Install Allure - run: | - curl -o allure-2.24.1.tgz -OLs https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/2.24.1/allure-commandline-2.24.1.tgz - sudo tar -zxvf allure-2.24.1.tgz -C /opt/ - sudo ln -s /opt/allure-2.24.1/bin/allure /usr/bin/allure - allure --version - - - name: Cache Python dependencies - uses: actions/cache@v3 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip- - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - - name: Run UI tests with Allure - run: | - behave --tags=@ui - continue-on-error: true - - - name: Run API tests with Allure - run: | - behave --tags=@api - continue-on-error: true - - - name: Generate Allure Report - if: always() - run: | - allure generate allure-results --clean -o allure-report - - - name: Upload Allure Report - uses: actions/upload-artifact@v4 - if: always() - with: - name: allure-report - path: allure-report - - - name: Upload Screenshots - uses: actions/upload-artifact@v4 - if: failure() - with: - name: failure-screenshots - path: test_reports/screenshots/ - - - name: Deploy Allure Report to GitHub Pages - if: always() - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./allure-report - publish_branch: gh-pages \ No newline at end of file + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set Up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Dependencies + run: | + python -m venv .venv + source .venv/bin/activate + pip install --upgrade pip + pip install -r requirements.txt + + - name: Install Chrome & ChromeDriver + run: | + sudo apt-get update + sudo apt-get install -y google-chrome-stable + CHROME_VERSION=$(google-chrome --version | awk '{print $3}') + sudo apt-get install -y chromedriver + + - name: Run Behave Tests + run: | + source .venv/bin/activate + behave || true # Prevent pipeline failure on test failures + + - name: Upload Test Report + uses: actions/upload-artifact@v4 + with: + name: behave-test-report + path: report.txt \ No newline at end of file From d619d0df2d933596f9a558f082096d9b7eb7a2f9 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:25:37 +0100 Subject: [PATCH 10/18] pipeline v2.1 --- .github/workflows/test-automation.yml | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index c6884f6..ff095ea 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -27,12 +27,25 @@ jobs: pip install --upgrade pip pip install -r requirements.txt - - name: Install Chrome & ChromeDriver + - name: Install ChromeDriver run: | - sudo apt-get update - sudo apt-get install -y google-chrome-stable - CHROME_VERSION=$(google-chrome --version | awk '{print $3}') - sudo apt-get install -y chromedriver + CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) + CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") + curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" + unzip chromedriver.zip + chmod +x chromedriver + sudo mv chromedriver /usr/local/bin/ + chromedriver --version + + - name: Install ChromeDriver + run: | + CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) + CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") + curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" + unzip chromedriver.zip + chmod +x chromedriver + sudo mv chromedriver /usr/local/bin/ + chromedriver --version - name: Run Behave Tests run: | From 8931a494627bf040f952f23875ae6ea8afccb45f Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:26:24 +0100 Subject: [PATCH 11/18] pipeline v2.2 --- .github/workflows/test-automation.yml | 38 +++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index ff095ea..64035f7 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -27,25 +27,25 @@ jobs: pip install --upgrade pip pip install -r requirements.txt - - name: Install ChromeDriver - run: | - CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) - CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") - curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" - unzip chromedriver.zip - chmod +x chromedriver - sudo mv chromedriver /usr/local/bin/ - chromedriver --version - - - name: Install ChromeDriver - run: | - CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) - CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") - curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" - unzip chromedriver.zip - chmod +x chromedriver - sudo mv chromedriver /usr/local/bin/ - chromedriver --version +# - name: Install ChromeDriver +# run: | +# CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) +# CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") +# curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" +# unzip chromedriver.zip +# chmod +x chromedriver +# sudo mv chromedriver /usr/local/bin/ +# chromedriver --version + +# - name: Install ChromeDriver +# run: | +# CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) +# CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") +# curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" +# unzip chromedriver.zip +# chmod +x chromedriver +# sudo mv chromedriver /usr/local/bin/ +# chromedriver --version - name: Run Behave Tests run: | From 9fdd6937ecb1dddbec7988d83094563f70b5064e Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:33:47 +0100 Subject: [PATCH 12/18] testing allure generation and upload to github --- .github/workflows/test-automation.yml | 40 ++++++++++++--------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index 64035f7..e07e520 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -27,33 +27,27 @@ jobs: pip install --upgrade pip pip install -r requirements.txt -# - name: Install ChromeDriver -# run: | -# CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) -# CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") -# curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" -# unzip chromedriver.zip -# chmod +x chromedriver -# sudo mv chromedriver /usr/local/bin/ -# chromedriver --version - -# - name: Install ChromeDriver -# run: | -# CHROME_VERSION=$(google-chrome --version | cut -d ' ' -f 3 | cut -d '.' -f 1) -# CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROME_VERSION") -# curl -L -o chromedriver.zip "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" -# unzip chromedriver.zip -# chmod +x chromedriver -# sudo mv chromedriver /usr/local/bin/ -# chromedriver --version - - name: Run Behave Tests run: | source .venv/bin/activate behave || true # Prevent pipeline failure on test failures - - name: Upload Test Report + - name: Install Allure and Generate Report + run: | + sudo apt-add-repository ppa:qameta/allure -y + sudo apt-get update + sudo apt-get install -y allure + allure generate reports/allure-results --clean -o reports/allure-report + + - name: Upload Allure Report as Artifact uses: actions/upload-artifact@v4 with: - name: behave-test-report - path: report.txt \ No newline at end of file + name: allure-report + path: reports/allure-report + + - name: Deploy Allure Report to GitHub Pages + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: reports/allure-report \ No newline at end of file From d02dc962909d73f9ba5e20f47a3012aa7db5ae96 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:38:35 +0100 Subject: [PATCH 13/18] fixed ui execution in headless mode --- utilities/driver_factory.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/utilities/driver_factory.py b/utilities/driver_factory.py index 52dfb6a..0600be8 100644 --- a/utilities/driver_factory.py +++ b/utilities/driver_factory.py @@ -7,22 +7,26 @@ from webdriver_manager.chrome import ChromeDriverManager from webdriver_manager.firefox import GeckoDriverManager + class DriverFactory: @staticmethod def get_driver(browser_name): if browser_name.lower() == "chrome": options = webdriver.ChromeOptions() - options.add_argument("--start-maximized") # Open in maximized mode + options.add_argument("--headless=new") # Run in headless mode options.add_argument("--disable-gpu") # Disable GPU (fixes some rendering issues) options.add_argument("--no-sandbox") # Bypass OS security model (for Docker/Linux) options.add_argument("--disable-dev-shm-usage") # Prevent crashes in Docker/Linux + options.add_argument("--remote-debugging-port=9222") # Avoid session issues + options.add_argument("--user-data-dir=/tmp/chrome-user-data") # Unique session directory options.add_experimental_option("excludeSwitches", ["enable-automation"]) # Avoid detection options.add_experimental_option("useAutomationExtension", False) # Disable automation extension return webdriver.Chrome( service=Service(ChromeDriverManager().install()), options=options ) + elif browser_name.lower() == "firefox": return webdriver.Firefox(service=Service(GeckoDriverManager().install())) else: - raise ValueError(f"Browser {browser_name} is not supported") \ No newline at end of file + raise ValueError(f"Browser {browser_name} is not supported") From 28a89464c6f21dd67d499a2103a9877921ce5748 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:50:11 +0100 Subject: [PATCH 14/18] fix allure installation --- .github/workflows/test-automation.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index e07e520..ee6a188 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -30,14 +30,14 @@ jobs: - name: Run Behave Tests run: | source .venv/bin/activate - behave || true # Prevent pipeline failure on test failures + behave --tags=@login || true # Prevent pipeline failure on test failures - name: Install Allure and Generate Report run: | - sudo apt-add-repository ppa:qameta/allure -y - sudo apt-get update - sudo apt-get install -y allure - allure generate reports/allure-results --clean -o reports/allure-report + curl -sLo allure-2.24.0.tgz https://github.com/allure-framework/allure2/releases/download/2.24.0/allure-2.24.0.tgz + tar -zxvf allure-2.24.0.tgz + sudo mv allure-2.24.0 /opt/allure + sudo ln -s /opt/allure/bin/allure /usr/local/bin/allure - name: Upload Allure Report as Artifact uses: actions/upload-artifact@v4 From cfb646cedb33e613c5215992bcdae841ea09e473 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 19:55:43 +0100 Subject: [PATCH 15/18] test allure report deployments --- .github/workflows/test-automation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index ee6a188..ddfdcfd 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -46,7 +46,7 @@ jobs: path: reports/allure-report - name: Deploy Allure Report to GitHub Pages - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + if: github.event_name == 'push' uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} From 7eeb374b940e11bc996f4b25fad0f58a18f1d0af Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 20:15:26 +0100 Subject: [PATCH 16/18] test allure --- .github/workflows/test-automation.yml | 43 ++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index ddfdcfd..c44ef82 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -2,7 +2,7 @@ name: Test Automation on: push: - branches: [ main, master, deployment/pipeline] + branches: [ main, master, deployment/pipeline ] pull_request: branches: [ main, master ] workflow_dispatch: @@ -32,22 +32,45 @@ jobs: source .venv/bin/activate behave --tags=@login || true # Prevent pipeline failure on test failures - - name: Install Allure and Generate Report + - name: Install Allure run: | curl -sLo allure-2.24.0.tgz https://github.com/allure-framework/allure2/releases/download/2.24.0/allure-2.24.0.tgz tar -zxvf allure-2.24.0.tgz sudo mv allure-2.24.0 /opt/allure sudo ln -s /opt/allure/bin/allure /usr/local/bin/allure - - name: Upload Allure Report as Artifact - uses: actions/upload-artifact@v4 + - name: Load test report history + uses: actions/checkout@v3 + if: always() + continue-on-error: true with: - name: allure-report - path: reports/allure-report + ref: gh-pages + path: gh-pages - - name: Deploy Allure Report to GitHub Pages - if: github.event_name == 'push' + - name: Build test report + uses: simple-elf/allure-report-action@v1.7 + if: always() + with: + gh_pages: gh-pages + allure_history: allure-history + allure_results: reports/allure-report + + - name: Publish test report uses: peaceiris/actions-gh-pages@v3 + if: always() with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: reports/allure-report \ No newline at end of file + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: allure-history +# - name: Upload Allure Report as Artifact +# uses: actions/upload-artifact@v4 +# with: +# name: allure-report +# path: reports/allure-report +# +# - name: Deploy Allure Report to GitHub Pages +# if: github.event_name == 'push' +# uses: peaceiris/actions-gh-pages@v3 +# with: +# github_token: ${{ secrets.GITHUB_TOKEN }} +# publish_dir: reports/allure-report \ No newline at end of file From d265fa6378a63db81195bb3ca1667677080ca944 Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 20:44:06 +0100 Subject: [PATCH 17/18] code cleanup. Task is done --- .github/workflows/test-automation.yml | 14 +------------- features/api/api_client.py | 4 ++++ features/environment.py | 20 ++++++++------------ features/environment_base.py | 6 +++--- features/steps/api_steps.py | 4 ++++ features/steps/checkout_steps.py | 6 ------ features/steps/inventory_steps.py | 2 -- page_objects/cart_page.py | 4 ++++ page_objects/login_page.py | 4 ++++ 9 files changed, 28 insertions(+), 36 deletions(-) diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml index c44ef82..f755655 100644 --- a/.github/workflows/test-automation.yml +++ b/.github/workflows/test-automation.yml @@ -30,7 +30,7 @@ jobs: - name: Run Behave Tests run: | source .venv/bin/activate - behave --tags=@login || true # Prevent pipeline failure on test failures + behave || true # Prevent pipeline failure on test failures - name: Install Allure run: | @@ -62,15 +62,3 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} publish_branch: gh-pages publish_dir: allure-history -# - name: Upload Allure Report as Artifact -# uses: actions/upload-artifact@v4 -# with: -# name: allure-report -# path: reports/allure-report -# -# - name: Deploy Allure Report to GitHub Pages -# if: github.event_name == 'push' -# uses: peaceiris/actions-gh-pages@v3 -# with: -# github_token: ${{ secrets.GITHUB_TOKEN }} -# publish_dir: reports/allure-report \ No newline at end of file diff --git a/features/api/api_client.py b/features/api/api_client.py index c3a5573..d860dc5 100644 --- a/features/api/api_client.py +++ b/features/api/api_client.py @@ -1,3 +1,7 @@ +# © 2025 Serhii Suzanskyi +# Open-source and awesome! Use it, modify it, share it—just don’t break it. +# See LICENSE for details. + import requests import json import os diff --git a/features/environment.py b/features/environment.py index 5b02e41..50d154a 100644 --- a/features/environment.py +++ b/features/environment.py @@ -1,21 +1,18 @@ +import os +import shutil from datetime import datetime + import allure from allure_commons.types import AttachmentType from behave.model_core import Status +from behave.parser import parse_file from selenium import webdriver -from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options -import os -import shutil -import tempfile - -from behave.parser import parse_file +from selenium.webdriver.chrome.service import Service -from utilities.driver_factory import DriverFactory -from utilities.api_client import APIClient from config.users import Users -from behave.model import Scenario -from behave.model import Table +from utilities.api_client import APIClient +from utilities.driver_factory import DriverFactory def create_chrome_options(): @@ -103,8 +100,7 @@ def before_feature(context, feature): # Set up Chrome options and user data directory chrome_options = create_chrome_options() - # context.user_data_dir = user_data_dir - + # Initialize the WebDriver service = Service() context.driver = webdriver.Chrome(service=service, options=chrome_options) diff --git a/features/environment_base.py b/features/environment_base.py index 73c9bd3..947ceac 100644 --- a/features/environment_base.py +++ b/features/environment_base.py @@ -2,13 +2,13 @@ # Open-source and awesome! Use it, modify it, share it—just don’t break it. # See LICENSE for details. -from selenium.webdriver.support.wait import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from selenium.common.exceptions import TimeoutException import logging import os from datetime import datetime +from selenium.webdriver.support.wait import WebDriverWait + + class TestBase: def __init__(self, driver): self.driver = driver diff --git a/features/steps/api_steps.py b/features/steps/api_steps.py index 558495b..8de90d7 100644 --- a/features/steps/api_steps.py +++ b/features/steps/api_steps.py @@ -1,3 +1,7 @@ +# © 2025 Serhii Suzanskyi +# Open-source and awesome! Use it, modify it, share it—just don’t break it. +# See LICENSE for details. + from behave import when, then from features.api.api_client import ApiClient import json diff --git a/features/steps/checkout_steps.py b/features/steps/checkout_steps.py index c0f6483..b298161 100644 --- a/features/steps/checkout_steps.py +++ b/features/steps/checkout_steps.py @@ -25,12 +25,6 @@ def step_impl(context): @then('the order summary should show') def step_impl(context): - """ - Verify order summary from table: - | Item total | $39.98 | - | Tax | $3.20 | - | Total | $43.18 | - """ expected_values = {row[0]: Decimal(row[1].replace('$', '')) for row in context.table} actual_values = { diff --git a/features/steps/inventory_steps.py b/features/steps/inventory_steps.py index e84717e..9dbe569 100644 --- a/features/steps/inventory_steps.py +++ b/features/steps/inventory_steps.py @@ -17,7 +17,6 @@ def step_impl(context): assert context.inventory_page.is_on_inventory_page(), \ f"Expected to be on the Inventory page, but currently on: {context.driver.current_url}" - @then('I should see {count:d} products listed') def step_impl(context, count): actual_count = context.inventory_page.get_product_count() @@ -60,7 +59,6 @@ def step_impl(context, product_name): context.inventory_page.add_to_cart(product_data["id"]) - @then('the cart badge should show "{count}"') def step_impl(context, count): actual_count = context.inventory_page.get_cart_badge_count() diff --git a/page_objects/cart_page.py b/page_objects/cart_page.py index dbc872c..37e873e 100644 --- a/page_objects/cart_page.py +++ b/page_objects/cart_page.py @@ -1,3 +1,7 @@ +# © 2025 Serhii Suzanskyi +# Open-source and awesome! Use it, modify it, share it—just don’t break it. +# See LICENSE for details. + from selenium.webdriver.common.by import By from .base_page import BasePage diff --git a/page_objects/login_page.py b/page_objects/login_page.py index 02e1fb8..8531d4a 100644 --- a/page_objects/login_page.py +++ b/page_objects/login_page.py @@ -1,3 +1,7 @@ +# © 2025 Serhii Suzanskyi +# Open-source and awesome! Use it, modify it, share it—just don’t break it. +# See LICENSE for details. + from selenium.webdriver.common.by import By from .base_page import BasePage From 5a9d28f51664b796d1a1196a1ab7bf81fc2f02dc Mon Sep 17 00:00:00 2001 From: "serhii.suzanskyi" Date: Thu, 27 Feb 2025 21:04:59 +0100 Subject: [PATCH 18/18] modified base page to show real product defects on allure report, previously it was classified at test defect --- page_objects/base_page.py | 45 +++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/page_objects/base_page.py b/page_objects/base_page.py index 8175ab5..ab57309 100644 --- a/page_objects/base_page.py +++ b/page_objects/base_page.py @@ -4,6 +4,7 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException class BasePage: def __init__(self, driver): @@ -12,23 +13,45 @@ def __init__(self, driver): def wait_for_page_load(self): """Waits until the document is fully loaded (ready state = 'complete').""" - self.wait.until(lambda d: d.execute_script("return document.readyState") == "complete") + try: + self.wait.until(lambda d: d.execute_script("return document.readyState") == "complete") + except TimeoutException: + raise AssertionError("❌ Page did not load properly! Possible product defect.") def find_element(self, locator): - return self.wait.until(EC.presence_of_element_located(locator)) + """Finds a single element and asserts that it exists.""" + try: + element = self.wait.until(EC.presence_of_element_located(locator)) + assert element is not None, f"❌ Element {locator} not found! Possible product defect." + return element + except TimeoutException: + raise AssertionError(f"❌ Element {locator} NOT found within time! Possible product defect.") def find_elements(self, locator): - return self.wait.until(EC.presence_of_all_elements_located(locator)) + """Finds multiple elements and asserts that at least one exists.""" + try: + elements = self.wait.until(EC.presence_of_all_elements_located(locator)) + assert len(elements) > 0, f"❌ No elements found for {locator}! Possible product defect." + return elements + except TimeoutException: + raise AssertionError(f"❌ Elements {locator} NOT found within time! Possible product defect.") def click(self, locator): + """Waits for an element to be clickable before clicking.""" self.wait_for_page_load() - element = self.wait.until(EC.presence_of_element_located(locator)) - self.driver.execute_script("arguments[0].click();", element) + try: + element = self.wait.until(EC.element_to_be_clickable(locator)) + assert element.is_displayed() and element.is_enabled(), f"❌ Element {locator} is NOT clickable! Possible product defect." + self.driver.execute_script("arguments[0].click();", element) + except TimeoutException: + raise AssertionError(f"❌ Element {locator} is NOT clickable within time! Possible product defect.") def input_text(self, locator, text): - element = self.wait.until(EC.presence_of_element_located(locator)) - element.clear() - element.send_keys(text) - - - + """Waits for an element to be visible and editable before sending text.""" + try: + element = self.wait.until(EC.presence_of_element_located(locator)) + assert element.is_displayed() and element.is_enabled(), f"❌ Element {locator} is NOT editable! Possible product defect." + element.clear() + element.send_keys(text) + except TimeoutException: + raise AssertionError(f"❌ Unable to input text into {locator}! Possible product defect.")