执行 QA 自动化项目时 CircleCI 出现错误

问题描述 投票:0回答:1

我目前正在从事一个 QA 自动化项目,特别是实施一个在执行完成后删除测试环境的解决方案。我有一个名为

setup.py
的 Python 脚本,它可以在我的本地环境中有效地执行此任务。该脚本正确安排了测试环境的删除。

为了提供更多详细信息,该脚本创建测试环境,然后触发 delete_organization 函数,该函数会自动安排在当天晚上晚些时候删除测试环境。该脚本在 CircleCI 上自动执行。

这个文件还将数据写入到

.env
文件中,这个文件看起来像这样,它执行的所有内容都是这样的

ORGANIZATION=Pedro
SCHOOL_YEAR=2024
APP_BASE_URL=https://qaa-kristen-richards-1719497724.dev.company.com
API_BASE_URL=https://api.dev.company.com
USER_PASSWORD=Xno2R0c5X3BPNEpw
API_USER_PASSWORD=Qm1hM0tPIXBpKSYr

setup.py
文件

"""
Setup

This script helps to create a new organization and generates a .env file with
the base URLs of Enroll and Sync Service products.
"""
import time
from enroll.common import create_env_file_content
from enroll.organization import default_organization_data, CreateOrganizationService
from enroll.common.utils.password import decode_password
from enroll.common.api.base_api import BaseApi
import os
import datetime
import requests


from argparse import ArgumentParser


def create_organization(payload, env):
    return CreateOrganizationService(env).perform(payload)


def four_digit_argument(value):
    ivalue = int(value)
    if ivalue < 1000 or ivalue > 9999:
        raise Exception("%s is not a valid 4-digit integer")
    return ivalue

if __name__ == '__main__':

    organization_data_payload = default_organization_data()
    parser = ArgumentParser()

    parser.add_argument(
        '-e',
        '--env',
        default='dev',
        type=str,
        required=False,
        help='Environment to setup the Golden Master to trigger the Suite against it',
        choices=('sandbox', 'dev', 'staging', 'test', 'performance', 'local', 'demand'),
    )

    parser.add_argument(
        '-o',
        '--org',
        default='',
        type=str,
        required=False,
        help='Organization to setup the Golden Master to trigger the Suite against it',
    )

    parser.add_argument(
        '-y',
        '--year',
        default=0,
        type=four_digit_argument,
        required=False,
        help='Organization to setup the Golden Master to trigger the Suite against it',
    )

    parser.add_argument(
        '-p',
        '--password',
        default=organization_data_payload['automation_password'],
        required=False,
        help='Automation user password for the created organization'
    )

    parser.add_argument(
        '-ap',
        '--apipassword',
        default=organization_data_payload['automationapi_password'],
        required=False,
        help='Automation Api user password for the created organization'
    )

    args = parser.parse_args()
    ## running the org on a pre existing org is determined only by the presence of both parameters, org and year. 
    ## when both are given it is assumed that the organization exists in the specified environment
    is_a_pre_existing_org = args.org != '' and args.year != 0
    ## by default use the organization data payload if is not a pre-existing-org 
    if not is_a_pre_existing_org:
        args.year = organization_data_payload['school_years'][0]
        ## only set the org name for not pre existing orgs if it was not given by parameter,
        ## there is a valid scenario of specifying the org name for the creation (by omiting the year)
        if args.org == '':
            args.org = organization_data_payload['subdomain']


    organization_data_payload['subdomain'] = args.org
    organization_data_payload['school_years'] = [args.year, args.year - 1]

    # when the organization already exists
    # then we skip the creation step, the password for the automation user
    # is pre-defined, later we could improve this part by allowing the script
    # to automatically create the [email protected] and
    # [email protected] users automatically when those doesn't exist already
    if is_a_pre_existing_org:
        args.password = decode_password('U1VoQ1ZVNVhaRllvY3pCQQ==')
        args.apipassword = decode_password('U1VoQ1ZVNVhaRllvY3pCQQ==')
    else:
        response = create_organization(organization_data_payload, args.env)
        if response.status_code != 200:
            raise Exception(
                "An error occurred while creating a new organization", response)

    env_file_content = create_env_file_content(
        args.env, args.org, args.year, args.password, args.apipassword)

    env_file = open('.env', 'w')
    env_file.write(env_file_content)
    env_file.close()

    # Wait for the data to be created
    if args.env != 'local':
        time.sleep(50)

api_client = BaseApi()  # Create an instance of the BaseApi class
api_client.authenticate_account(credentials={'username': '[email protected]', 'password': decode_password(os.environ.get('USER_PASSWORD'))})

if BaseApi._current_username:
  username = BaseApi._current_username
  token = BaseApi._tokens.get(username)

def get_org_id(token):

  url = f"{os.environ.get('API_BASE_URL')}/secret/api/because/company/policies/{os.environ.get('ORGANIZATION')}"

  headers = {
      "Authorization": f"Bearer {token}",
      "organization": os.environ.get('ORGANIZATION'),
      "yr": os.environ.get('SCHOOL_YEAR'),
  }

  try:
    response = requests.get(url, headers=headers)
    response.raise_for_status()  # Raise an exception for non-2xx status codes
    data = response.json()  # Parse the JSON response

    # Extract the value of the "id" key
    if "id" in data["data"]:
        return data["data"]["id"]
    else:
        print(f'Key "id" not found in response data')
  except requests.exceptions.RequestException as e:
    print(f"An error occurred: {e}")

def delete_organization():

    current_datetime = datetime.datetime.now()
    formatted_date = current_datetime.strftime("%Y-%m-%d")

    url = "https://123456.execute-api.us-east-2.amazonaws.com/schedule/qaa"

    headers = {
        "X-KEY": "slkjfdksh2378374"
    }

    data = {
      "org_id": get_org_id(token),
      "org_subdomain": os.environ.get('ORGANIZATION'),
      "org_namespace": os.environ.get('ORGANIZATION'),
      "date_to_remove": formatted_date,
      "platform_stage": "development",
      "user": "[email protected]",
      "environment": "development"
    }

    response = requests.post(url, headers=headers, json=data)

    if response.status_code == 200:
        print("The organization will be deleted tonight.")
    else:
        error_message = response.json().get('message', "Unknown error")
        print(f"Error deleting organization: {error_message}")

delete_organization()

问题依赖于 CicleCI 的执行,它抛出此错误

ENVIRONMENT: dev
ORGANIZATION: -
YEAR: -
Traceback (most recent call last):
  File "setup.py", line 121, in <module>
    api_client.authenticate_account(credentials={'username': '[email protected]', 'password': decode_password(os.environ.get('USER_PASSWORD'))})
  File "/opt/automation/enroll/common/utils/password.py", line 14, in decode_password
    byte_password = bytes(password, encoding='UTF-8')
TypeError: encoding without a string argument

Exited with code exit status 1

password.py
文件看起来是这样的

import base64


def decode_password(password: str) -> str:
    """
    Decode a base64-encoded password string.

    Args:
        password (str): The base64-encoded password string to be decoded.

    Returns:
        str: The decoded password string.
    """
    byte_password = bytes(password, encoding='UTF-8')
    return base64.b64decode(byte_password).decode('UTF-8')


def encode_password(password: str) -> str:
    """
    Encode a password string using base64 encoding.

    Args:
        password (str): The password string to be encoded.

    Returns:
        str: The base64-encoded password string.
    """
    byte_password = password.encode('ascii')
    return base64.b64encode(byte_password).decode('UTF-8')

CircleCI 执行的

config_continuation.yml
看起来像这样

version: 2.1

orbs:
  slack: circleci/[email protected]

parameters:
  parallelism:
    type: integer
    default: 4

jobs:
  build:
    parallelism: << pipeline.parameters.parallelism >>
    working_directory: /opt/automation
    docker:
      - image: enggaccounts/pytest-base-ci:0.0.2
        auth:
          username: $DOCKERHUB_USER
          password: $DOCKERHUB_PASSWORD
    environment:
      USER_PASSWORD: your_password_here # Add your password here

    steps:

      - run: 
          name: Figuring Enroll Data 
          # it will figure:
          # environment,
          # organization,
          # year,
          # all based on a branch naming convention
          # by default if the standard isn't followed it will assume the defaults 
          # (defaults implies creating a new org on the development environment and running the suite for current year)
          # the naming convention is as follows:
          # ENROLL_123_mycustom_automation_branch_ENV_demand_ORG_mycustom-org-123_YEAR_2023
          # what matters is what comes after _ENV_
          command: |
              if echo $CIRCLE_BRANCH | grep -q "_ENV_"; then
                params_str="${CIRCLE_BRANCH#*_ENV_}"
                env=${params_str/_*}
                org=''
                year=''
                is_custom='0'
                
                if echo $CIRCLE_BRANCH | grep -q "_ORG_"; then
                  params_str="${CIRCLE_BRANCH#*_ORG_}"
                  org="${params_str/_*}"
                fi

                if echo $CIRCLE_BRANCH | grep -q "_YEAR_"; then
                  params_str="${CIRCLE_BRANCH#*_YEAR_}"
                  year="${params_str/_*}"
                fi

                if echo $CIRCLE_BRANCH | grep -q "_CUSTOM"; then
                  is_custom='1'
                  echo "export IS_CUSTOM=${is_custom}" >> "$BASH_ENV"
                fi

                if [ ${#env} -gt 0 ]; then
                  echo "$env"
                  echo "export ENROLL_ENVIRONMENT=${env}" >> "$BASH_ENV"
                else
                  echo "Custom branch pattern detected but ENV was not defined!"
                fi

                if [ ${#org} -gt 0 ]; then
                  echo "$org"
                  echo "export ENROLL_ORGANIZATION=${org}" >> "$BASH_ENV"
                else
                  echo "Custom branch pattern detected but ORG was not defined!"
                fi

                if [ ${#year} -gt 0 ]; then
                  echo "$year"
                  echo "export ENROLL_YEAR=${year}" >> "$BASH_ENV"
                else
                  echo "Custom branch pattern detected but YEAR was not defined!"
                fi

              else
                echo "$env"
                echo 'export ENROLL_ENVIRONMENT="dev"' >> "$BASH_ENV"
              fi

      - checkout

      # Download and cache dependencies
      - restore_cache:
          keys:
            - v2-dependencies-{{ checksum "Pipfile.lock" }}
            # fallback to using the latest cache if no exact match is found
            - v2-dependencies-

      - run:
          name: Install pipenv
          command: sudo apt install -y pipenv

      - run:
          name: Install dependencies
          command: pipenv install

      - run:
          # SETUP the suite to run as specified,
          name: Setup the organization
          command: |
            if [ ${#ENROLL_ENVIRONMENT} -gt 0 ] && [ ${#ENROLL_ORGANIZATION} -gt 0 ] && [ ${#ENROLL_YEAR} -gt 0 ]; then
              echo "ENVIRONMENT: ${ENROLL_ENVIRONMENT}"
              echo "ORGANIZATION: ${ENROLL_ORGANIZATION}"
              echo "YEAR: ${ENROLL_YEAR}"
              pipenv run python3 setup.py -e ${ENROLL_ENVIRONMENT} -o ${ENROLL_ORGANIZATION} -y ${ENROLL_YEAR}
            elif [ ${#ENROLL_ENVIRONMENT} -gt 0 ]; then
              echo "ENVIRONMENT: ${ENROLL_ENVIRONMENT}"
              echo "ORGANIZATION: -"
              echo "YEAR: -"
              pipenv run python3 setup.py -e ${ENROLL_ENVIRONMENT}
            else
              echo "ENVIRONMENT: -"
              echo "ORGANIZATION: -"
              echo "YEAR: -"
              pipenv run python3 setup.py
            fi


      - run:
          name: Organization information
          command: |
            ORGANIZATION=$(grep 'ORGANIZATION=' .env | cut -d '=' -f2-)
            SCHOOL_YEAR=$(grep 'SCHOOL_YEAR=' .env | cut -d '=' -f2-)
            APP_BASE_URL=$(grep 'APP_BASE_URL=' .env | cut -d '=' -f2-)
            echo "Organization: $ORGANIZATION"
            echo "School Year: $SCHOOL_YEAR"
            echo "App base url: $APP_BASE_URL" 

      - save_cache:
          key: v2-dependencies-{{ checksum "Pipfile.lock" }}
          paths:
            - "~/.cache/pipenv"

      - run:
          name: Run tests
          command: |
            set -e
            TEST_FILES=$(circleci tests glob "enroll/**/tests/*.py" | circleci tests split --split-by=timings)
            mkdir -p reports

            if [[ "$IS_CUSTOM" == 1 ]]; then
                pipenv run pytest $TEST_FILES --html=reports/index.html --junitxml=reports/junit.xml -m custom

            else
                pipenv run pytest $TEST_FILES --html=reports/index.html --junitxml=reports/junit.xml
            fi

      - store_test_results:
          path: reports

      # Save artifacts
      - store_artifacts:
          path: reports

      # if you want to modify slack notification template
      # https://api.slack.com/reference/messaging/attachments
      # we are using attachments in order to help us see each message within it's own section
      - slack/notify:
          event: pass
          custom: |
            {
              "attachments": [
                {
                  "color": "#1cbf43",
                  "fallback": "A $CIRCLE_JOB job has succeeded!",
                  "text": "<https://github.com/$CIRCLE_USERNAME|$CIRCLE_USERNAME> A `$CIRCLE_JOB` job has succeeded!",
                  "fields": [
                    {
                      "title": "Project",
                      "value": "$CIRCLE_PROJECT_REPONAME",
                      "short": true
                    },
                    {
                      "title": "Branch",
                      "value": "$CIRCLE_BRANCH (<$CIRCLE_PULL_REQUEST|PR>)",
                      "short": true
                    }
                  ],
                  "actions": [
                    {
                      "type": "button",
                      "text": "Job $CIRCLE_BUILD_NUM",
                      "url": "$CIRCLE_BUILD_URL"
                    }
                  ]
                }
              ]
            }

      - slack/notify:
          event: fail
          custom: | # including template here so it is easier to customize
            {
              "attachments": [
                {
                  "color": "#ed5c5c",
                  "fallback": "A $CIRCLE_JOB job has failed. $CIRCLE_PR_USERNAME",
                  "text": "<https://github.com/$CIRCLE_USERNAME|$CIRCLE_USERNAME> A `$CIRCLE_JOB` job has failed!",
                  "fields": [
                    {
                      "title": "Project",
                      "value": "$CIRCLE_PROJECT_REPONAME",
                      "short": true
                    },
                    {
                      "title": "Branch",
                      "value": "$CIRCLE_BRANCH (<$CIRCLE_PULL_REQUEST|PR>)",
                      "short": true
                    }
                  ],
                  "actions": [
                    {
                      "type": "button",
                      "text": "Job $CIRCLE_BUILD_NUM",
                      "url": "$CIRCLE_BUILD_URL"
                    }
                  ]
                }
              ]
            }

workflows:
  build-workflow:
    jobs:
      - build

config.yml
看起来像这样

version: 2.1

setup: true

orbs:
  continuation: circleci/[email protected]

jobs:
  determine-parallelism:
    docker:
      - image: cimg/base:edge
    resource_class: medium
    steps:
      - checkout
      - run:
          name: Determine parallelism
          command: |
            # Put command/logic here to determine parallelism
            echo "determining"
            if echo $CIRCLE_BRANCH | grep -q "_ENV_" &&
                echo $CIRCLE_BRANCH | grep -q "_ORG_" &&
                echo $CIRCLE_BRANCH | grep -q "_YEAR_"; then
              echo "lets use 1"
              PARALLELISM=1
            else
              echo "lets use 4"
              PARALLELISM=4
            fi
            echo "lets create file"
            
            echo "{\"parallelism\": $PARALLELISM}" > pipeline-parameters.json
      
      - continuation/continue:
          configuration_path: .circleci/config_continuation.yml
          parameters: pipeline-parameters.json

workflows:
  build-setup:
    jobs:
      - determine-parallelism

如何修复此错误以使其在 CircleCI 上也能正常工作?请注意,无法在 YAML 文件中对密码进行硬编码,因为每次执行的密码都不同。 CircleCI 上的每次执行都会创建四个容器,每个容器都有不同的测试环境,以实现并行执行。我想我需要以某种方式传递这个值存储在

decode_password(os.environ.get('USER_PASSWORD'))

中的参数
python yaml pytest ui-automation circleci
1个回答
0
投票

idk但我从circleci收到错误,我认为可能是config.yml文件找不到满足要求的版本pipenv==2021.5.29(来自版本:)没有找到pipenv==2021.5的匹配发行版。 29

对于您的错误,您可能需要查看此

https://medium.com/@rajputgajanan50/typeerror-string-argument-without-an-encoding-in-python-957c225978d9

针对“TypeError:没有字符串参数的编码”错误

© www.soinside.com 2019 - 2024. All rights reserved.