Commit 8a691c5d authored by danfengliu's avatar danfengliu
Browse files

Add OIDC user onboard test in nightly


Signed-off-by: default avatardanfengliu <danfengl@vmware.com>
parent 25e0f161
......@@ -8,7 +8,5 @@ import replication
import repository
import swagger_client
class Harbor(project.Project, label.Label,
registry.Registry, replication.Replication,
repository.Repository):
class Harbor(project.Project):
pass
\ No newline at end of file
......@@ -13,7 +13,7 @@ class Artifact(base.Base, object):
client = self._get_client(**kwargs)
return client.list_artifacts(project_name, repo_name)
def get_reference_info(self, project_name, repo_name, reference, **kwargs):
def get_reference_info(self, project_name, repo_name, reference, ignore_not_found = False,**kwargs):
client = self._get_client(**kwargs)
params = {}
if "with_signature" in kwargs:
......@@ -22,7 +22,12 @@ class Artifact(base.Base, object):
params["with_tag"] = kwargs["with_tag"]
if "with_scan_overview" in kwargs:
params["with_scan_overview"] = kwargs["with_scan_overview"]
return client.get_artifact_with_http_info(project_name, repo_name, reference, **params)
try:
return client.get_artifact_with_http_info(project_name, repo_name, reference, **params)
except ApiException as e:
if e.status == 404 and ignore_not_found == True:
return []
def delete_artifact(self, project_name, repo_name, reference, expect_status_code = 200, expect_response_body = None, **kwargs):
client = self._get_client(**kwargs)
......@@ -62,10 +67,14 @@ class Artifact(base.Base, object):
base._assert_status_code(201, status_code)
return data
def create_tag(self, project_name, repo_name, reference, tag_name, expect_status_code = 201, **kwargs):
def create_tag(self, project_name, repo_name, reference, tag_name, expect_status_code = 201, ignore_conflict = False, **kwargs):
client = self._get_client(**kwargs)
tag = v2_swagger_client.Tag(name = tag_name)
_, status_code, _ = client.create_tag_with_http_info(project_name, repo_name, reference, tag)
try:
_, status_code, _ = client.create_tag_with_http_info(project_name, repo_name, reference, tag)
except ApiException as e:
if e.status == 409 and ignore_conflict == True:
return
base._assert_status_code(expect_status_code, status_code)
def delete_tag(self, project_name, repo_name, reference, tag_name, expect_status_code = 200, **kwargs):
......@@ -90,3 +99,9 @@ class Artifact(base.Base, object):
if scan_status == expected_scan_status:
return
raise Exception("Scan image result is {}, not as expected {}.".format(scan_status, expected_scan_status))
def check_reference_exist(self, project_name, repo_name, reference, ignore_not_found = False, **kwargs):
artifact = self.get_reference_info( project_name, repo_name, reference, ignore_not_found=ignore_not_found, **kwargs)
return {
0: False,
}.get(len(artifact), True)
......@@ -47,6 +47,14 @@ class Project(base.Base):
base._assert_status_code(200, status_code)
return data
def get_project_id(self, project_name, **kwargs):
project_data = self.get_projects(dict(), **kwargs)
actual_count = len(project_data)
if actual_count == 1 and str(project_data[0].project_name) != str(project_name):
return project_data[0].project_id
else:
return None
def projects_should_exist(self, params, expected_count = None, expected_project_id = None, **kwargs):
project_data = self.get_projects(params, **kwargs)
actual_count = len(project_data)
......
# -*- coding: utf-8 -*-
import site
reload(site)
import time
import base
import swagger_client
......@@ -13,7 +15,6 @@ def pull_harbor_image(registry, username, password, image, tag, expected_login_e
return
time.sleep(2)
ret = _docker_api.docker_image_pull(r'{}/{}'.format(registry, image), tag = tag, expected_error_message = expected_error_message)
print ret
def push_image_to_project(project_name, registry, username, password, image, tag, expected_login_error_message = None, expected_error_message = None, profix_for_image = None):
_docker_api = DockerAPI()
......@@ -40,14 +41,8 @@ def push_special_image_to_project(project_name, registry, username, password, im
time.sleep(2)
if expected_login_error_message != None:
return
return _docker_api.docker_image_build(r'{}/{}/{}'.format(registry, project_name, image), tags = tags, size=size, expected_error_message=expected_error_message)
return _docker_api.docker_image_build(r'{}/{}/{}'.format(registry, project_name, image), tags = tags, size=int(size), expected_error_message=expected_error_message)
def is_repo_exist_in_project(repositories, repo_name):
result = False
for reop in repositories:
if reop.name == repo_name:
return True
return result
class Repository(base.Base, object):
def __init__(self):
......@@ -118,16 +113,23 @@ class Repository(base.Base, object):
if tag.scan_overview != None:
raise Exception("Image should be <Not Scanned> state!")
def repository_should_exist(self, project_id, repo_name, **kwargs):
repositories = self.list_repositories(project_id, **kwargs)
if is_repo_exist_in_project(repositories, repo_name) == False:
def repository_should_exist(self, project_Name, repo_name, **kwargs):
repositories = self.list_repositories(project_Name, **kwargs)
if repo_name not in repositories:
raise Exception("Repository {} is not exist.".format(repo_name))
def check_repository_exist(self, project_Name, repo_name, **kwargs):
repositories = self.list_repositories(project_Name, **kwargs)
for repo in repositories:
print project_Name+"/"+repo_name
if repo.name == project_Name+"/"+repo_name:
return True
return False
def signature_should_exist(self, repo_name, tag, **kwargs):
signatures = self.get_repo_signatures(repo_name, **kwargs)
for each_sign in signatures:
if each_sign.tag == tag and len(each_sign.hashes["sha256"]) == 44:
print "sha256:", len(each_sign.hashes["sha256"])
return
raise Exception(r"Signature of {}:{} is not exist!".format(repo_name, tag))
......
......@@ -36,24 +36,25 @@ Pull image
Should Not Contain ${output} No such image:
Push image
[Arguments] ${ip} ${user} ${pwd} ${project} ${image} ${sha256}=${null} ${is_robot}=${false} ${tag_suffix}=${false}
${image_with_sha256}= Set Variable If '${sha256}'=='${null}' ${image} ${image}@sha256:${sha256}
${image_with_tag}= Set Variable If '${sha256}'=='${null}' ${image} ${image}:${sha256}
Sleep 3
Log To Console \nRunning docker push ${image}...
Docker Pull ${LOCAL_REGISTRY}/${LOCAL_REGISTRY_NAMESPACE}/${image_with_sha256}
# If no tag provided in $(image_with_or_without_tag}, latest will be the tag pulled from docker-hub or read from local
[Arguments] ${ip} ${user} ${pwd} ${project} ${image_with_or_without_tag} ${need_pull_first}=${true} ${sha256}=${null} ${is_robot}=${false}
${d}= Get Current Date result_format=%m%s
${image_with_tag}= Set Variable If '${tag_suffix}'=='${true}' ${image_with_tag}_${d} ${image_with_tag}
${image_in_use}= Set Variable If '${sha256}'=='${null}' ${image_with_or_without_tag} ${image_with_or_without_tag}@sha256:${sha256}
${image_in_use_with_tag}= Set Variable If '${sha256}'=='${null}' ${image_with_or_without_tag} ${image_with_or_without_tag}:${sha256}
Sleep 3
Log To Console \nRunning docker push ${image_with_or_without_tag}...
${image_in_use}= Set Variable If ${need_pull_first}==${true} ${image_in_use} ${image_with_or_without_tag}
Run Keyword If ${need_pull_first}==${true} Docker Pull ${LOCAL_REGISTRY}/${LOCAL_REGISTRY_NAMESPACE}/${image_in_use}
Run Keyword If ${is_robot}==${false} Wait Unitl Command Success docker login -u ${user} -p ${pwd} ${ip}
... ELSE Wait Unitl Command Success docker login -u robot\\\$${user} -p ${pwd} ${ip}
Wait Unitl Command Success docker tag ${LOCAL_REGISTRY}/${LOCAL_REGISTRY_NAMESPACE}/${image_with_sha256} ${ip}/${project}/${image_with_tag}
Wait Unitl Command Success docker push ${ip}/${project}/${image_with_tag}
Run Keyword If ${need_pull_first}==${true} Wait Unitl Command Success docker tag ${LOCAL_REGISTRY}/${LOCAL_REGISTRY_NAMESPACE}/${image_in_use} ${ip}/${project}/${image_in_use_with_tag}
... ELSE Wait Unitl Command Success docker tag ${image_in_use} ${ip}/${project}/${image_in_use_with_tag}
Wait Unitl Command Success docker push ${ip}/${project}/${image_in_use_with_tag}
Wait Unitl Command Success docker logout ${ip}
Sleep 1
[Return] ${image_with_tag}
Push Image With Tag
#tag1 is tag of image on docker hub,default latest,use a version existing if you do not want to use latest
#tag1 is tag of image on docker hub,default latest,use a existed version if you do not want to use latest
[Arguments] ${ip} ${user} ${pwd} ${project} ${image} ${tag} ${tag1}=latest
Log To Console \nRunning docker push ${image}...
Docker Pull ${LOCAL_REGISTRY}/${LOCAL_REGISTRY_NAMESPACE}/${image}:${tag1}
......
......@@ -202,7 +202,7 @@ Config Email
Input Text xpath=//*[@id='emailUsername'] example@vmware.com
Input Text xpath=//*[@id='emailPassword'] example
Input Text xpath=//*[@id='emailFrom'] example<example@vmware.com>
Sleep 1
Sleep 1
Retry Element Click xpath=//*[@id='emailSSL-wrapper']/label
Sleep 1
Retry Element Click xpath=//*[@id='emailInsecure-wrapper']/label
......@@ -341,3 +341,14 @@ Get Project Storage Quota Text From Project Quotas List
Switch To Project Quotas
${storage_quota}= Get Text xpath=//project-quotas//clr-datagrid//clr-dg-row[contains(.,'${project_name}')]//clr-dg-cell[3]//label
[Return] ${storage_quota}
Check Automatic Onboarding And Save
Retry Element Click ${cfg_auth_automatic_onboarding_checkbox}
Retry Element Click xpath=${config_auth_save_button_xpath}
Capture Page Screenshot
Set User Name Claim And Save
[Arguments] ${type}
Retry Text Input ${cfg_auth_user_name_claim_input} ${type}
Retry Element Click xpath=${config_auth_save_button_xpath}
Capture Page Screenshot
\ No newline at end of file
......@@ -31,6 +31,7 @@ ${gc_config_page} //clr-vertical-nav-group-children/a[contains(.,'Garbage')]
${gc_now_xpath} //*[@id='gc']/gc-config//button[contains(.,'GC')]
${gc_log_details_xpath} //*[@id='clr-dg-row26']/clr-dg-cell[6]/a
${configuration_system_tabsheet_id} //*[@id='config-system']
${configuration_authentication_tabsheet_id} //*[@id="config-auth"]
${configuration_project_quotas_tabsheet_id} //*[@id='config-quotas']
${configuration_system_wl_add_btn} //*[@id='show-add-modal-button']
${configuration_system_wl_textarea} //*[@id='allowlist-textarea']
......@@ -38,3 +39,8 @@ ${configuration_system_wl_add_confirm_btn} //*[@id='add-to-system']
${configuration_system_wl_delete_a_cve_id_icon} //system-settings/form/section//ul/li[1]/a[2]/clr-icon
${configuration_sys_repo_readonly_chb_id} //*[@id='repo_read_only_lbl']
${checkbox_delete_untagged_artifacts} //gc-config//clr-toggle-wrapper/label[contains(@for,'delete_untagged')]
${cfg_auth_automatic_onboarding_checkbox} //clr-checkbox-wrapper//label[contains(@for,'oidcAutoOnboard')]
${cfg_auth_user_name_claim_input} //*[@id='oidcUserClaim']
......@@ -21,12 +21,13 @@ Resource ../../resources/Util.robot
*** Keywords ***
Sign In Harbor With OIDC User
[Arguments] ${url} ${username}=${OIDC_USERNAME}
${head_username}= Set Variable xpath=//harbor-app/harbor-shell/clr-main-container/navigator/clr-header//clr-dropdown//button[contains(.,'${username}')]
[Arguments] ${url} ${username}=${OIDC_USERNAME} ${is_onboard}=${false} ${username_claim}=${null}
${full_name}= Set Variable ${username}@example.com
${head_username}= Set Variable If '${username_claim}' == 'email' xpath=//harbor-app/harbor-shell/clr-main-container/navigator/clr-header//clr-dropdown//button[contains(.,'${full_name}')] xpath=//harbor-app/harbor-shell/clr-main-container/navigator/clr-header//clr-dropdown//button[contains(.,'${username}')]
Init Chrome Driver
Go To ${url}
Retry Element Click ${log_oidc_provider_btn}
Retry Text Input ${dex_login_btn} ${username}@example.com
Retry Text Input ${dex_login_btn} ${full_name}
Retry Text Input ${dex_pwd_btn} password
Retry Element Click ${submit_login_btn}
Retry Element Click ${grant_btn}
......@@ -35,8 +36,12 @@ Sign In Harbor With OIDC User
# but if this user has been logged into harbor successfully, this input box will not show up,
# so there is condition branch for this stituation.
${isVisible}= Run Keyword And Return Status Element Should Be Visible ${oidc_username_input}
Run Keyword If ${is_onboard} == ${true} Should Not Be True ${isVisible}
Run Keyword If '${isVisible}' == 'True' Run Keywords Retry Text Input ${oidc_username_input} ${username} AND Retry Element Click ${save_btn}
Retry Wait Element ${head_username}
${name_display}= Get Text xpath=//harbor-app/harbor-shell/clr-main-container/navigator/clr-header//clr-dropdown[2]//button/span
Run Keyword If '${username_claim}' == 'email' Should Be Equal As Strings ${name_display} ${full_name}
... ELSE Should Be Equal As Strings ${name_display} ${username}
Get Secrete By API
[Arguments] ${url} ${username}=${OIDC_USERNAME}
......
......@@ -19,7 +19,7 @@ Resource ../../resources/Util.robot
*** Variables ***
*** Keywords ***
Filter Replicatin Rule
Filter Replication Rule
[Arguments] ${ruleName}
${rule_name_element}= Set Variable xpath=//clr-dg-cell[contains(.,'${ruleName}')]
Retry Element Click ${filter_rules_btn}
......@@ -72,8 +72,8 @@ Create A New Endpoint
Run Keyword If '${save}' == 'N' No Operation
Create A Rule With Existing Endpoint
[Arguments] ${name} ${replication_mode} ${project_name} ${resource_type} ${endpoint} ${dest_namespace}
... ${mode}=Manual ${cron}="* */59 * * * *" ${del_remote}=${false}
[Arguments] ${name} ${replication_mode} ${filter_project_name} ${resource_type} ${endpoint} ${dest_namespace}
... ${mode}=Manual ${cron}="* */59 * * * *" ${del_remote}=${false} ${filter_tag}=${false}
#click new
Retry Element Click ${new_name_xpath}
#input name
......@@ -82,7 +82,8 @@ Create A Rule With Existing Endpoint
... ELSE Run Keywords Retry Element Click ${replication_mode_radio_pull} AND Select Source Registry ${endpoint}
#set filter
Retry Text Input ${source_project} ${project_name}
Retry Text Input ${filter_name_id} ${filter_project_name}
Run Keyword If '${filter_tag}' != '${false}' Retry Text Input ${filter_tag_id} ${filter_tag}
Run Keyword And Ignore Error Select From List By Value ${rule_resource_selector} ${resource_type}
Retry Text Input ${dest_namespace_xpath} ${dest_namespace}
#set trigger
......
......@@ -62,7 +62,8 @@ ${rule_save_button} //button[contains(.,'SAVE')]
${provider_selector} //*[@id='adapter']
${replication_mode_radio_push} //clr-main-container//hbr-create-edit-rule//label[contains(.,'Push-based')]
${replication_mode_radio_pull} //clr-main-container//hbr-create-edit-rule//label[contains(.,'Pull-based')]
${source_project} //input[@id='filter_name']
${filter_name_id} //input[@id='filter_name']
${filter_tag_id} //input[@id='filter_tag']
${rule_resource_selector} //*[@id='select_resource']
${trigger_mode_selector} //*[@id='ruleTrigger']
${dest_namespace_xpath} //*[@id='dest_namespace']
......@@ -71,7 +72,6 @@ ${edit_replication_rule_id} //*[@id='edit_replication_rule_id']
${delete_replication_rule_id} //*[@id='delete_replication_rule_id']
${replication_exec_id} //*[@id='replication_exe_id']
${replication_task_line_1} //clr-datagrid//clr-dg-row/div/div[2]//clr-checkbox-wrapper/label[1]
${filter_tag} //*[@id='filter_tag']
${is_overide_xpath} //label[contains(.,'Replace the destination resources if name exists')]
${enable_rule_xpath} //label[contains(.,'Enable rule')]
${targetCron_id} //*[@id='targetCron']
......
......@@ -270,8 +270,8 @@ Verify Replicationrule
\ ${endpoint0}= Set Variable @{endpoint}[0]
\ Log To Console -----endpoint0-----${endpoint0}------------
\ @{endpoint_type}= Get Value From Json ${json} $.endpoint[?(@.name=${endpoint0})].type
\ Retry Textfield Value Should Be ${source_project} @{name_filters}[0]
\ Retry Textfield Value Should Be ${filter_tag} @{tag_filters}[0]
\ Retry Textfield Value Should Be ${filter_name_id} @{name_filters}[0]
\ Retry Textfield Value Should Be ${filter_tag_id} @{tag_filters}[0]
\ Retry Textfield Value Should Be ${rule_name_input} ${replicationrule}
\ Retry Textfield Value Should Be ${dest_namespace_xpath} @{dest_namespace}[0]
\ Log To Console -----endpoint_type-----@{endpoint_type}[0]------------
......
......@@ -233,7 +233,7 @@ Retry Keyword N Times When Error
\ Log To Console Trying ${keyword} elements @{elements} ${n} times ...
\ ${out} Run Keyword And Ignore Error ${keyword} @{elements}
\ Log To Console Return value is ${out} and ${out[0]}
\ Capture Page Screenshot record
\ Capture Page Screenshot record.png
\ Run Keyword If '${keyword}'=='Make Swagger Client' Exit For Loop If '${out[0]}'=='PASS' and '${out[1]}'=='0'
\ ... ELSE Exit For Loop If '${out[0]}'=='PASS'
\ Sleep 10
......
......@@ -30,13 +30,14 @@ Test Case - Garbage Collection
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
Create An New Project And Go Into Project project${d}
Push Image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} hello-world
Push Image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} redis
Sleep 2
Go Into Project project${d}
Delete Repo project${d}
Sleep 2
GC Now ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
Retry GC Should Be Successful 1 0 blobs marked, 3 blobs and 0 manifests eligible for deletion
Retry GC Should Be Successful 1 7 blobs and 1 manifests eligible for deletion
Retry GC Should Be Successful 1 The GC job actual frees up 34 MB space
Close Browser
Test Case - GC Untagged Images
......
......@@ -42,10 +42,6 @@ Test Case - OIDC User Sign In
Sleep 2
Sign In Harbor With OIDC User ${HARBOR_URL} test7
Sleep 2
Sign In Harbor With OIDC User ${HARBOR_URL} test8
Sleep 2
Sign In Harbor With OIDC User ${HARBOR_URL} test9
Sleep 2
Close Browser
Test Case - Create An New Project
......@@ -88,4 +84,19 @@ Test Case - Helm CLI Push
Init Chrome Driver
Sign In Harbor With OIDC User ${HARBOR_URL}
${secret}= Get Secrete By API ${HARBOR_URL}
Helm CLI Push Without Sign In Harbor ${OIDC_USERNAME} ${secret}
\ No newline at end of file
Helm CLI Push Without Sign In Harbor ${OIDC_USERNAME} ${secret}
Test Case - Onboard OIDC User Sign In
Init Chrome Driver
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
Switch To Configure
Check Automatic Onboarding And Save
Logout Harbor
Sign In Harbor With OIDC User ${HARBOR_URL} test8 is_onboard=${true}
Logout Harbor
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
Switch To Configure
Set User Name Claim And Save email
Logout Harbor
Sign In Harbor With OIDC User ${HARBOR_URL} test9 is_onboard=${true} username_claim=email
Sleep 2
\ No newline at end of file
......@@ -14,7 +14,7 @@
*** Settings ***
Documentation Harbor BATs
Library ../../apitests/python/library/Harbor.py ${SERVER_CONFIG}
Library ../../apitests/python/library/repository.py
Resource ../../resources/Util.robot
Default Tags Replication
......@@ -115,7 +115,7 @@ Test Case - Replication Rule Edit
Retry Text Input ${rule_name_input} ${rule_name_new}
Select Source Registry ${endpoint2}
#Source Resource Filter
Retry Text Input ${source_project} project${d}
Retry Text Input ${filter_name_id} project${d}
Select From List By Value ${rule_resource_selector} ${resource_type}
Retry Text Input ${dest_namespace_xpath} ${dest_namespace}
Select Trigger ${mode}
......@@ -125,7 +125,7 @@ Test Case - Replication Rule Edit
Edit Replication Rule By Name ${rule_name_new}
Retry Textfield Value Should Be ${rule_name_input} ${rule_name_new}
Retry List Selection Should Be ${src_registry_dropdown_list} ${endpoint2}-https://${ip}
Retry Textfield Value Should Be ${source_project} project${d}
Retry Textfield Value Should Be ${filter_name_id} project${d}
Retry Textfield Value Should Be ${dest_namespace_xpath} ${dest_namespace}
Retry List Selection Should Be ${rule_resource_selector} ${resource_type}
Retry List Selection Should Be ${rule_trigger_select} ${mode}
......@@ -268,7 +268,7 @@ Test Case - Replication Of Pull Images from Google-GCR To Self
Create A New Endpoint google-gcr e${d} asia.gcr.io ${null} ${gcr_ac_key} Y
Switch To Replication Manage
Create A Rule With Existing Endpoint rule${d} pull eminent-nation-87317/* image e${d} project${d}
Filter Replicatin Rule rule${d}
Filter Replication Rule rule${d}
Select Rule And Replicate rule${d}
Image Should Be Replicated To Project project${d} httpd
Image Should Be Replicated To Project project${d} tomcat
......@@ -278,7 +278,9 @@ Test Case - Replication Of Push Images to DockerHub Triggered By Event
Init Chrome Driver
${d}= Get Current Date result_format=%m%s
${sha256}= Set Variable 0e67625224c1da47cb3270e7a861a83e332f708d3d89dde0cbed432c94824d9a
${image}= Set Variable redis
${image}= Set Variable test_push_repli
${tag1}= Set Variable v1.1.0
@{tags} Create List ${tag1}
#login source
Sign In Harbor ${HARBOR_URL} ${HARBOR_ADMIN} ${HARBOR_PASSWORD}
Create An New Project And Go Into Project project${d}
......@@ -286,15 +288,15 @@ Test Case - Replication Of Push Images to DockerHub Triggered By Event
Create A New Endpoint docker-hub e${d} https://hub.docker.com/ danfengliu Aa123456 Y
Switch To Replication Manage
Create A Rule With Existing Endpoint rule${d} push project${d}/* image e${d} danfengliu mode=Event Based del_remote=${true}
${image_with_tag}= Push Image ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} project${d} ${image} sha256=${sha256} tag_suffix=${true}
Filter Replicatin Rule rule${d}
Push Special Image To Project project${d} ${ip} ${HARBOR_ADMIN} ${HARBOR_PASSWORD} ${image} tags=@{tags} size=12
Filter Replication Rule rule${d}
Select Rule rule${d}
Docker Image Can Be Pulled danfengliu/${image_with_tag} times=3
Docker Image Can Be Pulled danfengliu/${image}:${tag1} times=3
Executions Result Count Should Be Succeeded event_based 1
Go Into Project project${d}
Delete Repo project${d}
Docker Image Can Not Be Pulled danfengliu/${image_with_tag}
Docker Image Can Not Be Pulled danfengliu/${image}:${tag1}
Switch To Replication Manage
Filter Replicatin Rule rule${d}
Filter Replication Rule rule${d}
Select Rule rule${d}
Executions Result Count Should Be Succeeded event_based 2
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment