diff --git a/.github/workflows/test-automation.yml b/.github/workflows/test-automation.yml
new file mode 100644
index 00000000..d0c2412d
--- /dev/null
+++ b/.github/workflows/test-automation.yml
@@ -0,0 +1,133 @@
+name: Test Automation Content Processing
+
+on:
+ push:
+ branches:
+ - main
+ - dev
+ paths:
+ - 'tests/e2e-test/**'
+ schedule:
+ - cron: '0 13 * * *' # Runs at 1 PM UTC
+ workflow_dispatch:
+
+env:
+ url: ${{ vars.CP_WEB_URL }}
+ accelerator_name: "Content Processing"
+
+jobs:
+ test:
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.12'
+
+ - name: Azure CLI Login
+ uses: azure/login@v2
+ with:
+ creds: '{"clientId":"${{ secrets.AZURE_MAINTENANCE_CLIENT_ID }}","clientSecret":"${{ secrets.AZURE_MAINTENANCE_CLIENT_SECRET }}","subscriptionId":"${{ secrets.AZURE_MAINTENANCE_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.AZURE_TENANT_ID }}"}'
+
+ - name: Start Container App
+ id: start-container-app
+ uses: azure/cli@v2
+ with:
+ azcliversion: 'latest'
+ inlineScript: |
+ az rest -m post -u "/subscriptions/${{ secrets.AZURE_MAINTENANCE_SUBSCRIPTION_ID }}/resourceGroups/${{ vars.CP_RG }}/providers/Microsoft.App/containerApps/${{ vars.CP_CONTAINERAPP_PREFIX }}-app/start?api-version=2025-01-01"
+ az rest -m post -u "/subscriptions/${{ secrets.AZURE_MAINTENANCE_SUBSCRIPTION_ID }}/resourceGroups/${{ vars.CP_RG }}/providers/Microsoft.App/containerApps/${{ vars.CP_CONTAINERAPP_PREFIX }}-api/start?api-version=2025-01-01"
+ az rest -m post -u "/subscriptions/${{ secrets.AZURE_MAINTENANCE_SUBSCRIPTION_ID }}/resourceGroups/${{ vars.CP_RG }}/providers/Microsoft.App/containerApps/${{ vars.CP_CONTAINERAPP_PREFIX }}-web/start?api-version=2025-01-01"
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r tests/e2e-test/requirements.txt
+
+ - name: Ensure browsers are installed
+ run: python -m playwright install --with-deps chromium
+
+ - name: Run tests(1)
+ id: test1
+ run: |
+ xvfb-run pytest --headed --html=report/report.html --self-contained-html
+ working-directory: tests/e2e-test
+ continue-on-error: true
+
+ - name: Sleep for 30 seconds
+ if: ${{ steps.test1.outcome == 'failure' }}
+ run: sleep 30s
+ shell: bash
+
+ - name: Run tests(2)
+ id: test2
+ if: ${{ steps.test1.outcome == 'failure' }}
+ run: |
+ xvfb-run pytest --headed --html=report/report.html --self-contained-html
+ working-directory: tests/e2e-test
+ continue-on-error: true
+
+ - name: Sleep for 60 seconds
+ if: ${{ steps.test2.outcome == 'failure' }}
+ run: sleep 60s
+ shell: bash
+
+ - name: Run tests(3)
+ id: test3
+ if: ${{ steps.test2.outcome == 'failure' }}
+ run: |
+ xvfb-run pytest --headed --html=report/report.html --self-contained-html
+ working-directory: tests/e2e-test
+
+ - name: Upload test report
+ id: upload_report
+ uses: actions/upload-artifact@v4
+ if: ${{ !cancelled() }}
+ with:
+ name: test-report
+ path: tests/e2e-test/report/*
+
+ - name: Send Notification
+ if: always()
+ run: |
+ RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
+ REPORT_URL=${{ steps.upload_report.outputs.artifact-url }}
+ IS_SUCCESS=${{ steps.test1.outcome == 'success' || steps.test2.outcome == 'success' || steps.test3.outcome == 'success' }}
+ # Construct the email body
+ if [ "$IS_SUCCESS" = "true" ]; then
+ EMAIL_BODY=$(cat <Dear Team,
We would like to inform you that the ${{ env.accelerator_name }} Test Automation process has completed successfully.
Run URL: ${RUN_URL}
Test Report: ${REPORT_URL}
Best regards,
Your Automation Team
",
+ "subject": "${{ env.accelerator_name }} Test Automation - Success"
+ }
+ EOF
+ )
+ else
+ EMAIL_BODY=$(cat <Dear Team,We would like to inform you that the ${{ env.accelerator_name }} Test Automation process has encountered an issue and has failed to complete successfully.
Run URL: ${RUN_URL}
${OUTPUT}
Test Report: ${REPORT_URL}
Please investigate the matter at your earliest convenience.
Best regards,
Your Automation Team
",
+ "subject": "${{ env.accelerator_name }} Test Automation - Failure"
+ }
+ EOF
+ )
+ fi
+
+ # Send the notification
+ curl -X POST "${{ secrets.EMAILNOTIFICATION_LOGICAPP_URL_TA }}" \
+ -H "Content-Type: application/json" \
+ -d "$EMAIL_BODY" || echo "Failed to send notification"
+
+ - name: Stop Container App
+ if: always()
+ uses: azure/cli@v2
+ with:
+ azcliversion: 'latest'
+ inlineScript: |
+ az rest -m post -u "/subscriptions/${{ secrets.AZURE_MAINTENANCE_SUBSCRIPTION_ID }}/resourceGroups/${{ vars.CP_RG }}/providers/Microsoft.App/containerApps/${{ vars.CP_CONTAINERAPP_PREFIX }}-app/stop?api-version=2025-01-01"
+ az rest -m post -u "/subscriptions/${{ secrets.AZURE_MAINTENANCE_SUBSCRIPTION_ID }}/resourceGroups/${{ vars.CP_RG }}/providers/Microsoft.App/containerApps/${{ vars.CP_CONTAINERAPP_PREFIX }}-api/stop?api-version=2025-01-01"
+ az rest -m post -u "/subscriptions/${{ secrets.AZURE_MAINTENANCE_SUBSCRIPTION_ID }}/resourceGroups/${{ vars.CP_RG }}/providers/Microsoft.App/containerApps/${{ vars.CP_CONTAINERAPP_PREFIX }}-web/stop?api-version=2025-01-01"
+ az logout
\ No newline at end of file
diff --git a/tests/e2e-test/.gitignore b/tests/e2e-test/.gitignore
new file mode 100644
index 00000000..79644b65
--- /dev/null
+++ b/tests/e2e-test/.gitignore
@@ -0,0 +1,169 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+microsoft/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
+archive/
+report/
+screenshots/
+report.html
+assets/
+.vscode/
diff --git a/tests/e2e-test/base/__init__.py b/tests/e2e-test/base/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/e2e-test/base/base.py b/tests/e2e-test/base/base.py
new file mode 100644
index 00000000..5992ab6a
--- /dev/null
+++ b/tests/e2e-test/base/base.py
@@ -0,0 +1,10 @@
+class BasePage:
+ def __init__(self, page):
+ self.page = page
+
+ def scroll_into_view(self, locator):
+ reference_list = locator
+ locator.nth(reference_list.count() - 1).scroll_into_view_if_needed()
+
+ def is_visible(self, locator):
+ locator.is_visible()
diff --git a/tests/e2e-test/config/constants.py b/tests/e2e-test/config/constants.py
new file mode 100644
index 00000000..f5f4c9ac
--- /dev/null
+++ b/tests/e2e-test/config/constants.py
@@ -0,0 +1,8 @@
+import os
+
+from dotenv import load_dotenv
+
+load_dotenv()
+URL = os.getenv("url")
+if URL.endswith("/"):
+ URL = URL[:-1]
diff --git a/tests/e2e-test/pages/HomePage.py b/tests/e2e-test/pages/HomePage.py
new file mode 100644
index 00000000..ce091f44
--- /dev/null
+++ b/tests/e2e-test/pages/HomePage.py
@@ -0,0 +1,266 @@
+import os.path
+
+from base.base import BasePage
+from playwright.sync_api import expect
+
+
+class HomePage(BasePage):
+ TITLE_TEXT = "//span[normalize-space()='Processing Queue']"
+ SELECT_SCHEMA = "//input[@placeholder='Select Schema']"
+ IMPORT_CONTENT = "//button[normalize-space()='Import Content']"
+ REFRESH = "//button[normalize-space()='Refresh']"
+ BROWSE_FILES = "//button[normalize-space()='Browse Files']"
+ UPLOAD_BTN = "//button[normalize-space()='Upload']"
+ SUCCESS_MSG = "/div[@class='file-item']//*[name()='svg']"
+ CLOSE_BTN = "//button[normalize-space()='Close']"
+ STATUS = "//div[@role='cell']"
+ PROCESS_STEPS = "//button[@value='process-history']"
+ EXTRACT = "//span[normalize-space()='extract']"
+ MAP = "//span[normalize-space()='map']"
+ EVALUATE = "//span[normalize-space()='evaluate']"
+ EXTRACTED_RESULT = "//button[@value='extracted-results']"
+ COMMENTS = "//textarea"
+ SAVE_BTN = "//button[normalize-space()='Save']"
+ EDIT_CONFIRM = "//div[@class='jer-confirm-buttons']//div[1]"
+ SHIPPING_ADD_STREET = "//textarea[@id='shipping_address.street_textarea']"
+ DELETE_FILE = "//button[@aria-haspopup='menu']"
+
+ # INVOICE_JSON_ENTITIES
+ CUSTOMER_NAME = "//div[@id='customer_name_display']"
+ CUSTOMER_STREET = "//div[@id='customer_address.street_display']"
+ CUSTOMER_CITY = "//div[@id='customer_address.city_display']"
+ CUSTOMER_ZIP_CODE = "//div[@id='customer_address.postal_code_display']"
+ CUSTOMER_COUNTRY = "//div[@id='customer_address.country_display']"
+ SHIPPING_STREET = "//div[@id='shipping_address.street_display']"
+ SHIPPING_CITY = "//div[@id='shipping_address.city_display']"
+ SHIPPING_POSTAL_CODE = "//div[@id='shipping_address.postal_code_display']"
+ SHIPPING_COUNTRY = "//div[@id='shipping_address.country_display']"
+ PURCHASE_ORDER = "//div[@id='purchase_order_display']"
+ INVOICE_ID = "//div[@id='invoice_id_display']"
+ INVOICE_DATE = "//div[@id='invoice_date_display']"
+ payable_by = "//div[@id='payable_by_display']"
+ vendor_name = "//div[@id='vendor_name_display']"
+ v_street = "//div[@id='vendor_address.street_display']"
+ v_city = "//div[@id='vendor_address.city_display']"
+ v_state = "//div[@id='vendor_address.state_display']"
+ v_zip_code = "//div[@id='vendor_address.postal_code_display']"
+ vendor_tax_id = "//div[@id='vendor_tax_id_display']"
+ SUBTOTAL = "//span[normalize-space()='16859.1']"
+ TOTAL_TAX = "//span[normalize-space()='11286']"
+ INVOICE_TOTAL = "//span[normalize-space()='22516.08']"
+ PAYMENT_TERMS = "//div[@id='payment_terms_display']"
+ product_code1 = "//div[@id='items.0.product_code_display']"
+ p1_description = "//div[@id='items.0.description_display']"
+ p1_quantity = "//span[normalize-space()='163']"
+ p1_tax = "//span[normalize-space()='2934']"
+ p1_unit_price = "//span[normalize-space()='2.5']"
+ p1_total = "//span[normalize-space()='407.5']"
+
+ # PROPERTY_JSON_DATA
+
+ first_name = "//div[@id='policy_claim_info.first_name_display']"
+ last_name = "//div[@id='policy_claim_info.last_name_display']"
+ tel_no = "//div[@id='policy_claim_info.telephone_number_display']"
+ policy_no = "//div[@id='policy_claim_info.policy_number_display']"
+ coverage_type = "//div[@id='policy_claim_info.coverage_type_display']"
+ claim_number = "//div[@id='policy_claim_info.claim_number_display']"
+ policy_effective_date = (
+ "//div[@id='policy_claim_info.policy_effective_date_display']"
+ )
+ policy_expiration_date = (
+ "//div[@id='policy_claim_info.policy_expiration_date_display']"
+ )
+ damage_deductible = "//span[normalize-space()='1000']"
+ damage_deductible_currency = (
+ "//div[@id='policy_claim_info.damage_deductible_currency_display']"
+ )
+ date_of_damage_loss = "//div[@id='policy_claim_info.date_of_damage_loss_display']"
+ time_of_loss = "//div[@id='policy_claim_info.time_of_loss_display']"
+ date_prepared = "//div[@id='policy_claim_info.date_prepared_display']"
+ item = "//div[@id='property_claim_details.0.item_display']"
+ description = "//div[@id='property_claim_details.0.description_display']"
+ date_acquired = "//div[@id='property_claim_details.0.date_acquired_display']"
+ cost_new = "//body[1]/div[1]/div[1]/div[1]/div[1]/main[1]/div[1]/div[2]/div[2]/div[2]/div[3]/div[1]/div[1]/div[2]/div[1]/div[1]/div[3]/div[2]/div[1]/div[3]/div[1]/div[1]/div[3]/div[4]/div[1]/div[1]/div[1]/div[1]/span[1]"
+ cost_new_currency = (
+ "//div[@id='property_claim_details.0.cost_new_currency_display']"
+ )
+ replacement_repair = "//span[normalize-space()='350']"
+ replacement_repair_currency = (
+ "//div[@id='property_claim_details.0.replacement_repair_currency_display']"
+ )
+
+ def __init__(self, page):
+ self.page = page
+
+ def validate_home_page(self):
+ expect(self.page.locator(self.TITLE_TEXT)).to_be_visible()
+ self.page.wait_for_timeout(2000)
+
+ def select_schema(self, SchemaName):
+ self.page.wait_for_timeout(5000)
+ self.page.locator(self.SELECT_SCHEMA).click()
+ if SchemaName == "Invoice":
+ self.page.get_by_role("option", name="Invoice").click()
+ else:
+ self.page.get_by_role("option", name="Property Loss Damage Claim").click()
+
+ def upload_files(self, schemaType):
+ with self.page.expect_file_chooser() as fc_info:
+ self.page.locator(self.IMPORT_CONTENT).click()
+ self.page.locator(self.BROWSE_FILES).click()
+ self.page.wait_for_timeout(5000)
+ # self.page.wait_for_load_state('networkidle')
+ file_chooser = fc_info.value
+ current_working_dir = os.getcwd()
+ file_path1 = os.path.join(
+ current_working_dir, "testdata", "FabrikamInvoice_1.pdf"
+ )
+ file_path2 = os.path.join(current_working_dir, "testdata", "ClaimForm_1.pdf")
+
+ if schemaType == "Invoice":
+ file_chooser.set_files([file_path1])
+ else:
+ file_chooser.set_files([file_path2])
+ self.page.wait_for_timeout(5000)
+ self.page.wait_for_load_state("networkidle")
+ self.page.locator(self.UPLOAD_BTN).click()
+ self.page.wait_for_timeout(10000)
+ expect(
+ self.page.get_by_role("alertdialog", name="Import Content")
+ .locator("path")
+ .nth(1)
+ ).to_be_visible()
+ self.page.locator(self.CLOSE_BTN).click()
+
+ def refresh(self):
+ status_ele = self.page.locator(self.STATUS).nth(2)
+ max_retries = 15
+
+ for i in range(max_retries):
+ status_text = status_ele.inner_text().strip()
+
+ if status_text == "Completed":
+ break
+ elif status_text == "Error":
+ raise Exception(
+ f"Process failed with status: 'Error' after {i + 1} retries."
+ )
+
+ self.page.locator(self.REFRESH).click()
+ self.page.wait_for_timeout(5000)
+ else:
+ # Executed only if the loop did not break (i.e., status is neither Completed nor Error)
+ raise Exception(
+ f"Process did not complete. Final status was '{status_text}' after {max_retries} retries."
+ )
+
+ def validate_invoice_extracted_result(self):
+ expect(self.page.locator(self.CUSTOMER_NAME)).to_contain_text(
+ "Paris Fashion Group SARL"
+ )
+ expect(self.page.locator(self.CUSTOMER_STREET)).to_contain_text(
+ "10 Rue de Rivoli"
+ )
+ expect(self.page.locator(self.CUSTOMER_CITY)).to_contain_text("Paris")
+ expect(self.page.locator(self.CUSTOMER_ZIP_CODE)).to_contain_text("75001")
+ expect(self.page.locator(self.CUSTOMER_COUNTRY)).to_contain_text("France")
+ expect(self.page.locator(self.SHIPPING_STREET)).to_contain_text(
+ "25 Avenue Montaigne"
+ )
+ expect(self.page.locator(self.SHIPPING_CITY)).to_contain_text("Paris")
+ expect(self.page.locator(self.SHIPPING_POSTAL_CODE)).to_contain_text("75008")
+ expect(self.page.locator(self.SHIPPING_COUNTRY)).to_contain_text("France")
+ expect(self.page.locator(self.PURCHASE_ORDER)).to_contain_text("PO-34567")
+ expect(self.page.locator(self.INVOICE_ID)).to_contain_text("INV-20231005")
+ expect(self.page.locator(self.INVOICE_DATE)).to_contain_text("2023-10-05")
+ expect(self.page.locator(self.INVOICE_DATE)).to_contain_text("2023-10-05")
+ expect(self.page.locator(self.payable_by)).to_contain_text("2023-11-04")
+ expect(self.page.locator(self.vendor_name)).to_contain_text(
+ "Fabrikam Unlimited Company"
+ )
+ expect(self.page.locator(self.v_street)).to_contain_text("Wilton Place")
+ expect(self.page.locator(self.v_city)).to_contain_text("Brooklyn")
+ expect(self.page.locator(self.v_state)).to_contain_text("NY")
+ expect(self.page.locator(self.v_zip_code)).to_contain_text("22345")
+ expect(self.page.locator(self.vendor_tax_id)).to_contain_text("FR123456789")
+ expect(self.page.locator(self.SUBTOTAL)).to_contain_text("16859.1")
+ expect(self.page.locator(self.TOTAL_TAX)).to_contain_text("11286")
+ expect(self.page.locator(self.INVOICE_TOTAL)).to_contain_text("22516.08")
+ expect(self.page.locator(self.PAYMENT_TERMS)).to_contain_text("Net 30")
+ expect(self.page.locator(self.product_code1)).to_contain_text("EM032")
+ expect(self.page.locator(self.p1_description)).to_contain_text(
+ "Item: Terminal Lug"
+ )
+ expect(self.page.locator(self.p1_quantity)).to_contain_text("163")
+ expect(self.page.locator(self.p1_tax)).to_contain_text("2934")
+ expect(self.page.locator(self.p1_unit_price)).to_contain_text("2.5")
+ expect(self.page.locator(self.p1_total)).to_contain_text("407.5")
+
+ def modify_and_submit_extracted_data(self):
+ self.page.get_by_text('"25 Avenue Montaigne"').dblclick()
+ self.page.locator(self.SHIPPING_ADD_STREET).fill("25 Avenue Montaigne updated")
+ self.page.locator(self.EDIT_CONFIRM).click()
+ self.page.locator(self.COMMENTS).fill("Updated Shipping street address")
+ self.page.locator(self.SAVE_BTN).click()
+ self.page.wait_for_timeout(6000)
+
+ def validate_process_steps(self):
+ self.page.locator(self.PROCESS_STEPS).click()
+ self.page.locator(self.EXTRACT).click()
+ self.page.wait_for_timeout(3000)
+ expect(self.page.get_by_text('"extract"')).to_be_visible()
+ expect(self.page.get_by_text('"Succeeded"')).to_be_visible()
+ self.page.locator(self.EXTRACT).click()
+ self.page.wait_for_timeout(3000)
+ self.page.locator(self.MAP).click()
+ self.page.wait_for_timeout(3000)
+ expect(self.page.get_by_text('"map"')).to_be_visible()
+ self.page.locator(self.MAP).click()
+ self.page.wait_for_timeout(3000)
+ self.page.locator(self.EVALUATE).click()
+ self.page.wait_for_timeout(3000)
+ expect(self.page.get_by_text('"evaluate"')).to_be_visible()
+ self.page.locator(self.EVALUATE).click()
+ self.page.wait_for_timeout(3000)
+ self.page.locator(self.EXTRACTED_RESULT).click()
+ self.page.wait_for_timeout(3000)
+
+ def validate_property_extracted_result(self):
+ expect(self.page.locator(self.first_name)).to_contain_text("Sophia")
+ expect(self.page.locator(self.last_name)).to_contain_text("Kim")
+ expect(self.page.locator(self.tel_no)).to_contain_text("646-555-0789")
+ expect(self.page.locator(self.policy_no)).to_contain_text("PH5678901")
+ expect(self.page.locator(self.coverage_type)).to_contain_text("Homeowners")
+ expect(self.page.locator(self.claim_number)).to_contain_text("CLM5432109")
+ expect(self.page.locator(self.policy_effective_date)).to_contain_text(
+ "2022-07-01"
+ )
+ expect(self.page.locator(self.policy_expiration_date)).to_contain_text(
+ "2023-07-01"
+ )
+ expect(self.page.locator(self.damage_deductible)).to_contain_text("1000")
+ expect(self.page.locator(self.damage_deductible_currency)).to_contain_text(
+ "USD"
+ )
+ expect(self.page.locator(self.date_of_damage_loss)).to_contain_text(
+ "2023-05-10"
+ )
+ expect(self.page.locator(self.time_of_loss)).to_contain_text("13:20")
+ expect(self.page.locator(self.date_prepared)).to_contain_text("2023-05-11")
+ expect(self.page.locator(self.item)).to_contain_text("Apple")
+ expect(self.page.locator(self.description)).to_contain_text(
+ '"High-performance tablet with a large, vibrant display'
+ )
+ expect(self.page.locator(self.date_acquired)).to_contain_text("2022-01-20")
+ expect(self.page.locator(self.cost_new)).to_contain_text("1100")
+ expect(self.page.locator(self.cost_new_currency)).to_contain_text("USD")
+ expect(self.page.locator(self.replacement_repair)).to_contain_text("350")
+ expect(self.page.locator(self.replacement_repair_currency)).to_contain_text(
+ "USD"
+ )
+
+ def delete_files(self):
+ self.page.locator(self.DELETE_FILE).nth(0).click()
+ self.page.get_by_role("menuitem", name="Delete").click()
+ self.page.get_by_role("button", name="Confirm").click()
+ self.page.wait_for_timeout(6000)
diff --git a/tests/e2e-test/pages/__init__.py b/tests/e2e-test/pages/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/e2e-test/pages/loginPage.py b/tests/e2e-test/pages/loginPage.py
new file mode 100644
index 00000000..0b412556
--- /dev/null
+++ b/tests/e2e-test/pages/loginPage.py
@@ -0,0 +1,36 @@
+from base.base import BasePage
+
+
+class LoginPage(BasePage):
+
+ EMAIL_TEXT_BOX = "//input[@type='email']"
+ NEXT_BUTTON = "//input[@type='submit']"
+ PASSWORD_TEXT_BOX = "//input[@type='password']"
+ SIGNIN_BUTTON = "//input[@id='idSIButton9']"
+ YES_BUTTON = "//input[@id='idSIButton9']"
+ PERMISSION_ACCEPT_BUTTON = "//input[@type='submit']"
+
+ def __init__(self, page):
+ self.page = page
+
+ def authenticate(self, username, password):
+ # login with username and password in web url
+ self.page.locator(self.EMAIL_TEXT_BOX).fill(username)
+ self.page.locator(self.NEXT_BUTTON).click()
+ # Wait for the password input field to be available and fill it
+ self.page.wait_for_load_state("networkidle")
+ # Enter password
+ self.page.locator(self.PASSWORD_TEXT_BOX).fill(password)
+ # Click on SignIn button
+ self.page.locator(self.SIGNIN_BUTTON).click()
+ # Wait for 5 seconds to ensure the login process completes
+ self.page.wait_for_timeout(20000) # Wait for 20 seconds
+ if self.page.locator(self.PERMISSION_ACCEPT_BUTTON).is_visible():
+ self.page.locator(self.PERMISSION_ACCEPT_BUTTON).click()
+ self.page.wait_for_timeout(10000)
+ else:
+ # Click on YES button
+ self.page.locator(self.YES_BUTTON).click()
+ self.page.wait_for_timeout(10000)
+ # Wait for the "Articles" button to be available and click it
+ self.page.wait_for_load_state("networkidle")
diff --git a/tests/e2e-test/pytest.ini b/tests/e2e-test/pytest.ini
new file mode 100644
index 00000000..76eb64fc
--- /dev/null
+++ b/tests/e2e-test/pytest.ini
@@ -0,0 +1,6 @@
+[pytest]
+log_cli = true
+log_cli_level = INFO
+log_file = logs/tests.log
+log_file_level = INFO
+addopts = -p no:warnings
diff --git a/tests/e2e-test/readme.MD b/tests/e2e-test/readme.MD
new file mode 100644
index 00000000..941d3653
--- /dev/null
+++ b/tests/e2e-test/readme.MD
@@ -0,0 +1,35 @@
+# cto-test-automation
+
+Write end-to-end tests for your web apps with [Playwright](https://github.com/microsoft/playwright-python) and [pytest](https://docs.pytest.org/en/stable/).
+
+- Support for **all modern browsers** including Chromium, WebKit and Firefox.
+- Support for **headless and headed** execution.
+- **Built-in fixtures** that provide browser primitives to test functions.
+
+Pre-Requisites:
+
+- Install Visual Studio Code: Download and Install Visual Studio Code(VSCode).
+- Install NodeJS: Download and Install Node JS
+
+Create and Activate Python Virtual Environment
+
+- From your directory open and run cmd : "python -m venv microsoft"
+This will create a virtual environment directory named microsoft inside your current directory
+- To enable virtual environment, copy location for "microsoft\Scripts\activate.bat" and run from cmd
+
+Installing Playwright Pytest from Virtual Environment
+
+- To install libraries run "pip install -r requirements.txt"
+- Install the required browsers "playwright install"
+
+Run test cases
+
+- To run test cases from your 'tests' folder : "pytest --html=report.html --self-contained-html"
+
+Create .env file in project root level with web app url and client credentials
+
+- create a .env file in project root level and the application url. please refer 'sample_dotenv_file.txt' file.
+
+## Documentation
+
+See on [playwright.dev](https://playwright.dev/python/docs/test-runners) for examples and more detailed information.
diff --git a/tests/e2e-test/requirements.txt b/tests/e2e-test/requirements.txt
new file mode 100644
index 00000000..7aad0cfb
--- /dev/null
+++ b/tests/e2e-test/requirements.txt
@@ -0,0 +1,6 @@
+pytest-playwright
+pytest-reporter-html1
+python-dotenv
+pytest-check
+pytest-html
+py
\ No newline at end of file
diff --git a/tests/e2e-test/testdata/ClaimForm_1.pdf b/tests/e2e-test/testdata/ClaimForm_1.pdf
new file mode 100644
index 00000000..85be1dbf
Binary files /dev/null and b/tests/e2e-test/testdata/ClaimForm_1.pdf differ
diff --git a/tests/e2e-test/testdata/FabrikamInvoice_1.pdf b/tests/e2e-test/testdata/FabrikamInvoice_1.pdf
new file mode 100644
index 00000000..cc3b7dba
Binary files /dev/null and b/tests/e2e-test/testdata/FabrikamInvoice_1.pdf differ
diff --git a/tests/e2e-test/tests/__init__.py b/tests/e2e-test/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/e2e-test/tests/conftest.py b/tests/e2e-test/tests/conftest.py
new file mode 100644
index 00000000..d356dc40
--- /dev/null
+++ b/tests/e2e-test/tests/conftest.py
@@ -0,0 +1,53 @@
+import os
+
+import pytest
+from config.constants import URL
+from playwright.sync_api import sync_playwright
+from py.xml import html # type: ignore
+
+
+@pytest.fixture(scope="session")
+def login_logout():
+ # perform login and browser close once in a session
+ with sync_playwright() as p:
+ browser = p.chromium.launch(headless=False, args=["--start-maximized"])
+ context = browser.new_context(no_viewport=True)
+ context.set_default_timeout(80000)
+ page = context.new_page()
+ # Navigate to the login URL
+ page.goto(URL, wait_until="domcontentloaded")
+ # login to web url with username and password
+ # login_page = LoginPage(page)
+ # load_dotenv()
+ # login_page.authenticate(os.getenv('user_name'), os.getenv('pass_word'))
+
+ yield page
+ # perform close the browser
+ browser.close()
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_html_report_title(report):
+ report.title = "Automation_Content_Processing"
+
+
+# Add a column for descriptions
+def pytest_html_results_table_header(cells):
+ cells.insert(1, html.th("Description"))
+
+
+def pytest_html_results_table_row(report, cells):
+ cells.insert(
+ 1, html.td(report.description if hasattr(report, "description") else "")
+ )
+
+
+# Add logs and docstring to report
+@pytest.hookimpl(hookwrapper=True)
+def pytest_runtest_makereport(item, call):
+ outcome = yield
+ report = outcome.get_result()
+ report.description = str(item.function.__doc__)
+ os.makedirs("logs", exist_ok=True)
+ extra = getattr(report, "extra", [])
+ report.extra = extra
diff --git a/tests/e2e-test/tests/test_contentProcessing_gp_tc.py b/tests/e2e-test/tests/test_contentProcessing_gp_tc.py
new file mode 100644
index 00000000..cbe99797
--- /dev/null
+++ b/tests/e2e-test/tests/test_contentProcessing_gp_tc.py
@@ -0,0 +1,41 @@
+import logging
+
+import pytest
+from pages.HomePage import HomePage
+
+logger = logging.getLogger(__name__)
+
+
+@pytest.mark.testcase_id("TC001")
+def test_ContentProcessing_Golden_path_test(login_logout):
+ """Validate Golden path test case for Content Processing Accelerator"""
+ page = login_logout
+ home_page = HomePage(page)
+ logger.info("Step 1: Validate home page is loaded.")
+ home_page.validate_home_page()
+ logger.info("Step 2: Select Invoice Schema.")
+ home_page.select_schema("Invoice")
+ logger.info("Step 3: Upload Invoice documents.")
+ home_page.upload_files("Invoice")
+ logger.info("Step 4: Refresh page till status is updated to Completed.")
+ home_page.refresh()
+ logger.info("Step 5: Validate extracted result for Invoice.")
+ home_page.validate_invoice_extracted_result()
+ logger.info("Step 6: Modify Extracted Data JSON & submit comments.")
+ home_page.modify_and_submit_extracted_data()
+ logger.info("Step 7: Validate process steps for Invoice")
+ home_page.validate_process_steps()
+ logger.info("Step 8: Select Property Loss Damage Claim Form Schema.")
+ home_page.select_schema("Property")
+ logger.info("Step 9: Upload Property Loss Damage Claim Form documents.")
+ home_page.upload_files("Property")
+ logger.info("Step 10: Refresh page till status is updated to Completed.")
+ home_page.refresh()
+ logger.info(
+ "Step 11: Validate extracted result for Property Loss Damage Claim Form."
+ )
+ home_page.validate_property_extracted_result()
+ logger.info("Step 12: Validate process steps for Property Loss Damage Claim Form.")
+ home_page.validate_process_steps()
+ logger.info("Step 13: Validate Delete files.")
+ home_page.delete_files()