Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jimfuqian/BB2-3239-fix-CI-check-selenium-tests #1271

Merged
merged 73 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
6e710d6
supply the ENV var needed by selenium test case: USE_NEW_PERM_SCREEN
JFU-NAVA-PBC Dec 3, 2024
39a008f
try headless on CI
JFU-NAVA-PBC Dec 4, 2024
97e187b
Merge remote-tracking branch 'origin/master' into jimfuqian/BB2-3239-…
JFU-NAVA-PBC Dec 4, 2024
c190c2f
use webdriver local.
JFU-NAVA-PBC Dec 4, 2024
5fdba74
fix linting
JFU-NAVA-PBC Dec 4, 2024
b19596a
unify the webdriver path on local and CI run context
JFU-NAVA-PBC Dec 4, 2024
0f13799
make CI selenium tests testing again.
JFU-NAVA-PBC Dec 5, 2024
e291c69
add docker platform spec., correct artifacts names.
JFU-NAVA-PBC Dec 5, 2024
0e7e4e7
add step to trouble shoot, step sh hanging...
JFU-NAVA-PBC Dec 6, 2024
5bd9f42
add step to trouble shoot, step sh hanging...
JFU-NAVA-PBC Dec 6, 2024
cc668cb
trouble shoot: sh in step is hanging.
JFU-NAVA-PBC Dec 6, 2024
6cee90d
replace make with pip install etc.
JFU-NAVA-PBC Dec 6, 2024
b3e5b8d
install newrelic 8.8.0
JFU-NAVA-PBC Dec 6, 2024
873383b
remove test step...
JFU-NAVA-PBC Dec 6, 2024
af06d84
satisfy deps that conflict...
JFU-NAVA-PBC Dec 6, 2024
c244b23
satisfy deps that conflict...
JFU-NAVA-PBC Dec 6, 2024
ddaab2e
CI docker: install python 3.11.9 on top of chromium base.
JFU-NAVA-PBC Dec 6, 2024
9c83f91
install sqlite3 on CI docker.
JFU-NAVA-PBC Dec 6, 2024
db22ece
fix the sqlite issue.
JFU-NAVA-PBC Dec 6, 2024
3785609
continue add missing native libs...
JFU-NAVA-PBC Dec 7, 2024
eee05e5
create python link to accomodate some scripts
JFU-NAVA-PBC Dec 7, 2024
2f38af0
adjust logging integration tests setup on CI node.
JFU-NAVA-PBC Dec 7, 2024
a4d25cf
adjust details in steps...
JFU-NAVA-PBC Dec 7, 2024
cb4dba6
use selenium headless on CI
JFU-NAVA-PBC Dec 7, 2024
7768f31
fix linting.
JFU-NAVA-PBC Dec 7, 2024
62570b9
fix logging integration tests schemas (selenium related)
JFU-NAVA-PBC Dec 8, 2024
b771188
quote out logit tests to trouble shoot
JFU-NAVA-PBC Dec 8, 2024
f53cb6d
new screen env var needed for app and user mgmt tests
JFU-NAVA-PBC Dec 8, 2024
5fb716d
add pytest -s to see stdout...
JFU-NAVA-PBC Dec 8, 2024
52d568f
increase headless window height.
JFU-NAVA-PBC Dec 8, 2024
9848964
create ui change to trouble shoot.
JFU-NAVA-PBC Dec 8, 2024
92cdf04
cleanup...
JFU-NAVA-PBC Dec 9, 2024
1da9135
cleanup...
JFU-NAVA-PBC Dec 9, 2024
f4eb53e
start selenium hub in pipeline
JFU-NAVA-PBC Dec 9, 2024
505858f
start selenium-server-standalone hub in pipeline...
JFU-NAVA-PBC Dec 9, 2024
0c2c9e5
locate the selenium jar
JFU-NAVA-PBC Dec 9, 2024
bcd8b66
verify hub is at localhost.
JFU-NAVA-PBC Dec 9, 2024
4293a2b
temp change hub url to localhost.
JFU-NAVA-PBC Dec 9, 2024
befc5d7
customize selenium grid host name for CI context.
JFU-NAVA-PBC Dec 9, 2024
4232996
set chromium location.
JFU-NAVA-PBC Dec 9, 2024
1234eba
add tracing for trouble shoot
JFU-NAVA-PBC Dec 10, 2024
eca410e
Merge remote-tracking branch 'origin/master' into jimfuqian/BB2-3239-…
JFU-NAVA-PBC Dec 12, 2024
53b412e
replace selenium docker image that is archived with new one.
JFU-NAVA-PBC Dec 13, 2024
16faadb
try DinD in pipeline.
JFU-NAVA-PBC Dec 13, 2024
4e2d515
install dependencies in CI ECR image
JFU-NAVA-PBC Dec 13, 2024
9a83637
comment out python packages install - since it is installed in the EC…
JFU-NAVA-PBC Dec 13, 2024
25e7dd9
revert CI ECR image to p311
JFU-NAVA-PBC Dec 13, 2024
fe62763
set pipeline selenium chromedriver location.
JFU-NAVA-PBC Dec 13, 2024
27fb616
refactor
JFU-NAVA-PBC Dec 13, 2024
76b8887
fix driver ver check step
JFU-NAVA-PBC Dec 13, 2024
8a1e3df
trouble shoot rendering timeout at hicn field...
JFU-NAVA-PBC Dec 13, 2024
ad6ffdd
trouble shoot..
JFU-NAVA-PBC Dec 13, 2024
fac95f7
fix add_argu syntax...
JFU-NAVA-PBC Dec 13, 2024
45224a4
size...
JFU-NAVA-PBC Dec 14, 2024
0f6dfe9
rearrange form fields of mock login...
JFU-NAVA-PBC Dec 14, 2024
7dd772f
try slsx since all env vars needed are set at the top of the pipeline.
JFU-NAVA-PBC Dec 14, 2024
10c5a32
local script cleanup, use mock login on CI
JFU-NAVA-PBC Dec 14, 2024
ddc068a
use mock and fix var syntax
JFU-NAVA-PBC Dec 14, 2024
3670123
dump bb2 log for diagnose info...
JFU-NAVA-PBC Dec 14, 2024
288fb34
diagnose...
JFU-NAVA-PBC Dec 14, 2024
3b290fb
diagnose...
JFU-NAVA-PBC Dec 14, 2024
f8cb998
diagnose...
JFU-NAVA-PBC Dec 14, 2024
0acee34
diagnose...
JFU-NAVA-PBC Dec 14, 2024
5f357d8
start mslsx needed by MSLSX mode...
JFU-NAVA-PBC Dec 14, 2024
8665f34
subshell mslsx start and moving on...
JFU-NAVA-PBC Dec 14, 2024
4c73874
fix url for mslsx calls
JFU-NAVA-PBC Dec 15, 2024
e80a995
fix url for medicare callback
JFU-NAVA-PBC Dec 15, 2024
9e45503
trigger CI check
JFU-NAVA-PBC Dec 15, 2024
f6203bc
Merge remote-tracking branch 'origin/master' into jimfuqian/BB2-3239-…
JFU-NAVA-PBC Dec 15, 2024
c4863af
added missed user info mslsx ENV var in pipeline
JFU-NAVA-PBC Dec 15, 2024
1a00033
fix proto method in user info url
JFU-NAVA-PBC Dec 15, 2024
55cabc2
clean up and finalize files naming...
JFU-NAVA-PBC Dec 16, 2024
4a7ebe3
fix pipeline ecr reference etc.
JFU-NAVA-PBC Dec 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,5 @@ WORKDIR /code
RUN python -m venv /tmp/venv
RUN . /tmp/venv/bin/activate
ENV PATH="/tmp/venv/bin:${PATH}"
RUN pip install --upgrade pip
RUN pip install --upgrade pip-tools
RUN pip install --upgrade setuptools
RUN pip install -r requirements/requirements.dev.txt --no-index --find-links ./vendor/
RUN pip install --upgrade pip pip-tools setuptools
RUN pip install -r requirements/requirements.dev.txt --no-index --find-links ./vendor/
13 changes: 7 additions & 6 deletions Dockerfile.selenium
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
FROM seleniarm/standalone-chromium
FROM selenium/standalone-chromium

ENV PYTHONUNBUFFERED 1
USER root
#RUN apt-get update ; apt-get install -yq git curl libpq-dev libffi-dev

RUN apt-get update ; apt-get install -yq python3 python3-venv
RUN ln -s /usr/bin/python3 /usr/local/bin/python
RUN useradd -m -s /bin/bash DEV
USER DEV

# switch to existing seluser from selenium docker
USER seluser

ADD . /code
WORKDIR /code
RUN python -m venv /tmp/venv
RUN . /tmp/venv/bin/activate
ENV PATH="/tmp/venv/bin:${PATH}"

RUN pip3 install --upgrade pip
RUN pip3 install selenium pytest debugpy jsonschema python-dateutil

Empty file.
4 changes: 2 additions & 2 deletions Jenkinsfiles/Jenkinsfile.cbc-run-multi-pr-checks-w-selenium
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pipeline {
steps{
sh """
. venv/bin/activate
pytest ./apps/integration_tests/logging_tests.py::TestLoggings::test_auth_fhir_flows_logging
USE_NEW_PERM_SCREEN=true pytest ./apps/integration_tests/logging_tests.py::TestLoggings::test_auth_fhir_flows_logging
"""
}
}
Expand Down Expand Up @@ -134,7 +134,7 @@ pipeline {
sh 'echo "RUN selenium tests - testclient based authorization flow tests and data flow tests"'
sh """
. venv/bin/activate
pytest ./apps/integration_tests/selenium_tests.py
USE_NEW_PERM_SCREEN=true pytest ./apps/integration_tests/selenium_tests.py
"""
}
}
Expand Down
151 changes: 151 additions & 0 deletions Jenkinsfiles/Jenkinsfile.cbc-run-multi-pr-checks-w-selenium-chromium
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
pipeline {
agent {
kubernetes {
defaultContainer "bb2-cbc-build-selenium-python311-chromium"
yamlFile "Jenkinsfiles/cbc-pod-deployment-config-w-selenium-p311-chromium.yaml"
}
}

environment {
DJANGO_LOG_JSON_FORMAT_PRETTY = true
DJANGO_SETTINGS_MODULE = "hhs_oauth_server.settings.logging_it"
OAUTHLIB_INSECURE_TRANSPORT = true
DJANGO_SECURE_SESSION = false
DJANGO_FHIR_CERTSTORE = "./certstore"
// use mock login - safer, faster
USE_MSLSX = true
DJANGO_MEDICARE_SLSX_REDIRECT_URI = "http://localhost:8000/mymedicare/sls-callback"
DJANGO_MEDICARE_SLSX_LOGIN_URI = "http://localhost:8080/sso/authorize?client_id=bb2api"
DJANGO_SLSX_HEALTH_CHECK_ENDPOINT = "http://localhost:8080/health"
DJANGO_SLSX_TOKEN_ENDPOINT = "http://localhost:8080/sso/session"
DJANGO_SLSX_SIGNOUT_ENDPOINT = "http://localhost:8080/sso/signout"
DJANGO_SLSX_USERINFO_ENDPOINT="http://localhost:8080/v1/users"
DJANGO_SLSX_CLIENT_ID = credentials("bb2-selenium-tests-slsx-client-id")
DJANGO_SLSX_CLIENT_SECRET = credentials("bb2-selenium-tests-slsx-client-secret")
DJANGO_USER_ID_ITERATIONS = credentials("bb2-integration-tests-bfd-iterations")
DJANGO_USER_ID_SALT = credentials("bb2-integration-tests-bfd-salt")
FHIR_CERT = credentials("bb2-integration-tests-bfd-cert")
FHIR_KEY = credentials("bb2-integration-tests-bfd-key")
FHIR_URL = "${params.FHIR_URL}"
HOSTNAME_URL = "http://localhost:8000"
}

parameters {
string(
name: 'FHIR_URL',
defaultValue: "https://prod-sbx.bfd.cms.gov",
description: 'The default FHIR URL for the back end BFD service.'
)
booleanParam(
name: 'RUN_SELENIUM_TESTS',
defaultValue: false,
description: 'Set to true, selenium tests will be run as part of integration tests'
)
}

stages {
stage("SETUP FHIR cert and key") {
steps {
writeFile(file: "${env.DJANGO_FHIR_CERTSTORE}/certstore/ca.cert.pem", text: readFile(env.FHIR_CERT))
writeFile(file: "${env.DJANGO_FHIR_CERTSTORE}/certstore/ca.key.nocrypt.pem", text: readFile(env.FHIR_KEY))
}
}

stage("INSTALL Python Packages") {
steps {
sh """
pip3 install --upgrade pip setuptools wheel
pip3 install -r requirements/requirements.dev.txt --no-index --find-links ./vendor/
"""
}
}

stage("CHECK Flake8 Python Lint/Style") {
steps{
sh """
flake8
"""
}
}

stage("START BB2 server in background") {
when {
expression { params.RUN_SELENIUM_TESTS == true }
}
steps{
sh """
mkdir ./docker-compose/tmp/
(python3 ./dev-local/app.py&) &&
python3 manage.py migrate &&
python3 manage.py create_admin_groups &&
python3 manage.py loaddata scopes.json &&
python3 manage.py create_blue_button_scopes &&
python3 manage.py create_test_user_and_application &&
python3 manage.py create_user_identification_label_selection &&
python3 manage.py create_test_feature_switches &&
(if [ ! -d 'bluebutton-css' ] ; then git clone https://github.com/CMSgov/bluebutton-css.git ; else echo 'CSS already installed.' ; fi) &&
echo 'starting bb2...' &&
(export DJANGO_SETTINGS_MODULE=hhs_oauth_server.settings.logging_it && python3 manage.py runserver 0.0.0.0:8000 > ./docker-compose/tmp/bb2_email_to_stdout.log 2>&1 &)
"""
}
}

stage("RUN logging integration tests") {
when {
expression { params.RUN_SELENIUM_TESTS == true }
}
steps{
catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') {
sh """
USE_NEW_PERM_SCREEN=true ON_REMOTE_CI=true pytest -s ./apps/integration_tests/logging_tests.py::TestLoggings::test_auth_fhir_flows_logging
"""
}
sh """
echo '======================'
cat ./docker-compose/tmp/bb2_email_to_stdout.log
echo '======================'
"""
}
}

stage("RUN selenium user and apps management tests") {
when {
expression { params.RUN_SELENIUM_TESTS == true }
}
steps{
sh 'echo "RUN selenium tests - user account and app management tests"'
sh """
USE_NEW_PERM_SCREEN=true ON_REMOTE_CI=true pytest -s ./apps/integration_tests/selenium_accounts_tests.py::TestUserAndAppMgmt::testAccountAndAppMgmt
"""
}
}

stage("RUN integration tests") {
steps{
sh """
python3 runtests.py --integration apps.integration_tests.integration_test_fhir_resources.IntegrationTestFhirApiResources
"""
}
}

stage("RUN Django Unit Tests") {
steps{
sh """
python3 runtests.py
"""
}
}

stage("RUN selenium tests") {
when {
expression { params.RUN_SELENIUM_TESTS == true }
}
steps{
sh 'echo "RUN selenium tests - testclient based authorization flow tests and data flow tests"'
sh """
USE_NEW_PERM_SCREEN=true ON_REMOTE_CI=true pytest -s ./apps/integration_tests/selenium_tests.py
"""
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Pod
spec:
containers:
- name: bb2-cbc-build-selenium-python311-chromium
image: "public.ecr.aws/f5g8o1y9/bb2-cbc-build-selenium-python311-chromium:latest"
tty: true
command: ["tail", "-f"]
imagePullPolicy: Always
nodeSelector:
Agents: true
14 changes: 7 additions & 7 deletions apps/integration_tests/log_event_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"auth_client_id": {"type": "string"},
"auth_app_id": {"type": "string"},
"auth_app_name": {"pattern": "TestApp"},
"auth_app_data_access_type": {"pattern": "RESEARCH_STUDY"},
"auth_app_data_access_type": {"pattern": "THIRTEEN_MONTH"},
"auth_share_demographic_scopes": {"pattern": "^True$"},
"auth_require_demographic_scopes": {"pattern": "^True$"},
},
Expand Down Expand Up @@ -94,7 +94,7 @@
"auth_client_id": {"type": "string"},
"auth_app_id": {"type": "string"},
"auth_app_name": {"pattern": "TestApp"},
"auth_app_data_access_type": {"pattern": "RESEARCH_STUDY"},
"auth_app_data_access_type": {"pattern": "THIRTEEN_MONTH"},
"auth_require_demographic_scopes": {"pattern": "^True$"},
"req_qparam_client_id": {"type": "string"},
"req_qparam_lang": {"type": "string"},
Expand Down Expand Up @@ -129,7 +129,7 @@
"auth_client_id": {"type": "string"},
"auth_app_id": {"type": "string"},
"auth_app_name": {"pattern": "TestApp"},
"auth_app_data_access_type": {"pattern": "RESEARCH_STUDY"},
"auth_app_data_access_type": {"pattern": "THIRTEEN_MONTH"},
"auth_require_demographic_scopes": {"pattern": "^True$"},
"path": {"pattern": "/mymedicare/login"},
"request_method": {"pattern": "GET"},
Expand Down Expand Up @@ -158,7 +158,7 @@
"auth_client_id": {"type": "string"},
"auth_app_id": {"type": "string"},
"auth_app_name": {"pattern": "TestApp"},
"auth_app_data_access_type": {"pattern": "RESEARCH_STUDY"},
"auth_app_data_access_type": {"pattern": "THIRTEEN_MONTH"},
"auth_crosswalk_action": {"enum": ["R", "C"]},
"auth_require_demographic_scopes": {"pattern": "^True$"},
"req_user_id": {"type": "number"},
Expand Down Expand Up @@ -195,7 +195,7 @@
"auth_client_id": {"type": "string"},
"auth_app_id": {"type": "string"},
"auth_app_name": {"pattern": "TestApp"},
"auth_app_data_access_type": {"pattern": "RESEARCH_STUDY"},
"auth_app_data_access_type": {"pattern": "THIRTEEN_MONTH"},
"auth_crosswalk_action": {"enum": ["R", "C"]},
"auth_require_demographic_scopes": {"pattern": "^True$"},
"req_qparam_client_id": {"type": "string"},
Expand Down Expand Up @@ -235,13 +235,13 @@
"auth_client_id": {"type": "string"},
"auth_app_id": {"type": "string"},
"auth_app_name": {"pattern": "TestApp"},
"auth_app_data_access_type": {"pattern": "RESEARCH_STUDY"},
"auth_app_data_access_type": {"pattern": "THIRTEEN_MONTH"},
"auth_crosswalk_action": {"enum": ["R", "C"]},
"auth_require_demographic_scopes": {"pattern": "^True$"},
"req_redirect_uri": {"type": "string"},
"req_scope": {"type": "string"},
"req_share_demographic_scopes": {"pattern": "^True$"},
"req_allow": {"pattern": "Allow"},
"req_allow": {"pattern": "Connect"},
"req_user_id": {"type": "integer"},
"req_user_username": {"type": "string"},
"req_fhir_id": {"type": "string"},
Expand Down
62 changes: 32 additions & 30 deletions apps/integration_tests/selenium_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,16 +576,17 @@ class Action(Enum):
"params": [20, By.ID, AUTH_SCREEN_ID_LANG, AUTH_SCREEN_EN_TXT]
},
# now check the expiration info section
{
"display": "Check for authorization screen expire info in English",
"action": Action.CONTAIN_TEXT,
"params": [20, By.ID, AUTH_SCREEN_ID_EXPIRE_INFO, AUTH_SCREEN_EN_EXPIRE_INFO_TXT]
},
{
"display": "Check en_US date format and validate",
"action": Action.CHECK_DATE_FORMAT,
"params": [20, By.ID, AUTH_SCREEN_ID_END_DATE, AUTH_SCREEN_EN_DATE_FORMAT, EN_US]
},
# comment out due to PROD TestApp now is RESEARCH_STUDY which does not have expire info
# {
# "display": "Check for authorization screen expire info in English",
# "action": Action.CONTAIN_TEXT,
# "params": [20, By.ID, AUTH_SCREEN_ID_EXPIRE_INFO, AUTH_SCREEN_EN_EXPIRE_INFO_TXT]
# },
# {
# "display": "Check en_US date format and validate",
# "action": Action.CHECK_DATE_FORMAT,
# "params": [20, By.ID, AUTH_SCREEN_ID_END_DATE, AUTH_SCREEN_EN_DATE_FORMAT, EN_US]
# },
# the 'approve' and 'deny' button click not using locale based text
# so it is lang agnostic
CLICK_AGREE_ACCESS
Expand Down Expand Up @@ -644,16 +645,17 @@ class Action(Enum):
"params": [20, By.ID, AUTH_SCREEN_ID_LANG, AUTH_SCREEN_ES_TXT]
},
# now check the expiration info section
{
"display": "Check for authorization screen expire info in Spanish",
"action": Action.CONTAIN_TEXT,
"params": [20, By.ID, AUTH_SCREEN_ID_EXPIRE_INFO, AUTH_SCREEN_ES_EXPIRE_INFO_TXT]
},
{
"display": "Check Spanish date format and validate",
"action": Action.CHECK_DATE_FORMAT,
"params": [20, By.ID, AUTH_SCREEN_ID_END_DATE, AUTH_SCREEN_ES_DATE_FORMAT, ES_ES]
},
# comment out due to on PROD TestApp is RESEARCH_STUDY which does not have expire info
# {
# "display": "Check for authorization screen expire info in Spanish",
# "action": Action.CONTAIN_TEXT,
# "params": [20, By.ID, AUTH_SCREEN_ID_EXPIRE_INFO, AUTH_SCREEN_ES_EXPIRE_INFO_TXT]
# },
# {
# "display": "Check Spanish date format and validate",
# "action": Action.CHECK_DATE_FORMAT,
# "params": [20, By.ID, AUTH_SCREEN_ID_END_DATE, AUTH_SCREEN_ES_DATE_FORMAT, ES_ES]
# },
# the 'approve' and 'deny' button click not using locale based text
# so it is lang agnostic
CLICK_AGREE_ACCESS
Expand All @@ -672,16 +674,16 @@ class Action(Enum):
"params": [20, By.ID, AUTH_SCREEN_ID_LANG, AUTH_SCREEN_ES_TXT]
},
# now check the expiration info section
{
"display": "Check for authorization screen expire info in Spanish",
"action": Action.CONTAIN_TEXT,
"params": [20, By.ID, AUTH_SCREEN_ID_EXPIRE_INFO, AUTH_SCREEN_ES_EXPIRE_INFO_TXT]
},
{
"display": "Check Spanish date format and validate",
"action": Action.CHECK_DATE_FORMAT,
"params": [20, By.ID, AUTH_SCREEN_ID_END_DATE, AUTH_SCREEN_ES_DATE_FORMAT, ES_ES]
},
# {
# "display": "Check for authorization screen expire info in Spanish",
# "action": Action.CONTAIN_TEXT,
# "params": [20, By.ID, AUTH_SCREEN_ID_EXPIRE_INFO, AUTH_SCREEN_ES_EXPIRE_INFO_TXT]
# },
# {
# "display": "Check Spanish date format and validate",
# "action": Action.CHECK_DATE_FORMAT,
# "params": [20, By.ID, AUTH_SCREEN_ID_END_DATE, AUTH_SCREEN_ES_DATE_FORMAT, ES_ES]
# },
# the 'approve' and 'deny' button click not using locale based text
# so it is lang agnostic
CLICK_AGREE_ACCESS
Expand Down
Loading
Loading