diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ec81ff7dd2..bae4755687 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -110,6 +110,8 @@ jobs: strategy: matrix: case: + - name: YarnQueueTest + class: org.apache.streampark.e2e.cases.YarnQueueTest - name: TokenManagementTest class: org.apache.streampark.e2e.cases.TokenManagementTest - name: UploadManagementTest diff --git a/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/cases/YarnQueueTest.java b/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/cases/YarnQueueTest.java new file mode 100644 index 0000000000..c649808b42 --- /dev/null +++ b/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/cases/YarnQueueTest.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.streampark.e2e.cases; + +import org.apache.streampark.e2e.core.StreamPark; +import org.apache.streampark.e2e.pages.LoginPage; +import org.apache.streampark.e2e.pages.setting.SettingPage; +import org.apache.streampark.e2e.pages.setting.YarnQueuePage; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.RemoteWebDriver; +import org.testcontainers.shaded.org.awaitility.Awaitility; + +import static org.assertj.core.api.Assertions.assertThat; + +@StreamPark(composeFiles = "docker/basic/docker-compose.yaml") +public class YarnQueueTest { + + private static RemoteWebDriver browser; + + private static final String userName = "admin"; + + private static final String password = "streampark"; + + private static final String teamName = "default"; + + private static final String newQueueLabel = "new_label"; + + private static final String editQueueLabel = "edit_label"; + + private static final String description = "test_description"; + + @BeforeAll + public static void setup() { + new LoginPage(browser) + .login(userName, password, teamName) + .goToNav(SettingPage.class) + .goToTab(YarnQueuePage.class); + } + + @Test + @Order(10) + void testYarnQueue() { + final YarnQueuePage queuePage = new YarnQueuePage(browser); + queuePage.createYarnQueue(newQueueLabel, description); + + Awaitility.await() + .untilAsserted( + () -> assertThat(queuePage.yarnQueueList()) + .as("Yarn Queue list should contain newly-created item") + .extracting(WebElement::getText) + .anyMatch(it -> it.contains(newQueueLabel)) + .anyMatch(it -> it.contains(description))); + } + + @Test + @Order(20) + void testCreateDuplicateYarnQueue() { + final YarnQueuePage queuePage = new YarnQueuePage(browser); + queuePage.createYarnQueue(newQueueLabel, description); + Awaitility.await() + .untilAsserted( + () -> assertThat(queuePage.errorMessageList()) + .as("Yarn Queue Duplicated Error message should be displayed") + .extracting(WebElement::getText) + .anyMatch(it -> it.contains("The queue label existed in the current team"))); + + queuePage.createYarnQueueForm().buttonCancel().click(); + } + + @Test + @Order(30) + void testEditYarnQueue() { + final YarnQueuePage queuePage = new YarnQueuePage(browser); + String editDescription = "edit_" + description; + + queuePage.editYarnQueue(newQueueLabel, editQueueLabel, editDescription); + + Awaitility.await() + .untilAsserted( + () -> assertThat(queuePage.yarnQueueList()) + .as("Yarn queue list should contain edited yarn queue") + .extracting(WebElement::getText) + .anyMatch(it -> it.contains(editQueueLabel)) + .anyMatch(it -> it.contains(editDescription))); + } + + @Test + @Order(40) + void testDeleteYarnQueue() { + final YarnQueuePage queuePage = new YarnQueuePage(browser); + + queuePage.deleteYarnQueue(editQueueLabel); + Awaitility.await() + .untilAsserted( + () -> { + assertThat(queuePage.yarnQueueList()) + .noneMatch(it -> it.getText().contains(editQueueLabel)); + }); + } +} diff --git a/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/common/NavBarPage.java b/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/common/NavBarPage.java index 4b7dc1cdb7..d8a0ae9615 100644 --- a/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/common/NavBarPage.java +++ b/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/common/NavBarPage.java @@ -19,6 +19,7 @@ import org.apache.streampark.e2e.pages.flink.ApacheFlinkPage; import org.apache.streampark.e2e.pages.resource.ResourcePage; +import org.apache.streampark.e2e.pages.setting.SettingPage; import org.apache.streampark.e2e.pages.system.SystemPage; import lombok.Getter; @@ -86,6 +87,17 @@ public T goToNav(Class nav) { return nav.cast(new ResourcePage(driver)); } + if (nav == SettingPage.class) { + new WebDriverWait(driver, Constants.DEFAULT_WEBDRIVER_WAIT_DURATION) + .until(ExpectedConditions.elementToBeClickable(settingsTab)); + String tabOpenStateXpath = + "//span[contains(@class, 'ml-2') and contains(@class, 'streampark-simple-menu-sub-title') and contains(text(), 'Settings')]/../parent::li[contains(@class, 'streampark-menu-opened')]"; + if (driver.findElements(By.xpath(tabOpenStateXpath)).isEmpty()) { + settingsTab.click(); + } + return nav.cast(new SettingPage(driver)); + } + throw new UnsupportedOperationException("Unknown nav bar"); } diff --git a/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/setting/SettingPage.java b/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/setting/SettingPage.java new file mode 100644 index 0000000000..1883191624 --- /dev/null +++ b/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/setting/SettingPage.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.streampark.e2e.pages.setting; + +import org.apache.streampark.e2e.pages.common.NavBarPage; + +import lombok.Getter; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import java.time.Duration; + +@Getter +public class SettingPage extends NavBarPage implements NavBarPage.NavBarItem { + + @FindBy(xpath = "//span[contains(@class, 'streampark-simple-menu-sub-title') and contains(text(), 'Yarn Queue')]//..") + private WebElement menuYarnQueueManagement; + + public SettingPage(RemoteWebDriver driver) { + super(driver); + } + + public T goToTab(Class tab) { + if (tab == YarnQueuePage.class) { + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.elementToBeClickable(menuYarnQueueManagement)); + menuYarnQueueManagement.click(); + return tab.cast(new YarnQueuePage(driver)); + } + + throw new UnsupportedOperationException("Unknown tab: " + tab.getName()); + } + + public interface Tab { + } +} diff --git a/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/setting/YarnQueuePage.java b/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/setting/YarnQueuePage.java new file mode 100644 index 0000000000..6b25b3af84 --- /dev/null +++ b/streampark-e2e/streampark-e2e-case/src/test/java/org/apache/streampark/e2e/pages/setting/YarnQueuePage.java @@ -0,0 +1,136 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.streampark.e2e.pages.setting; + +import org.apache.streampark.e2e.pages.common.Constants; +import org.apache.streampark.e2e.pages.common.NavBarPage; + +import lombok.Getter; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.PageFactory; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import java.util.List; + +@Getter +public class YarnQueuePage extends NavBarPage implements SettingPage.Tab { + + @FindBy(xpath = "//span[contains(., 'Yarn Queue List')]/..//button[contains(@class, 'ant-btn-primary')]/span[contains(text(), 'Add New')]") + private WebElement buttonCreateYarnQueue; + + @FindBy(xpath = "//tbody[contains(@class, 'ant-table-tbody')]") + private List yarnQueueList; + + @FindBy(className = "ant-form-item-explain-error") + private List errorMessageList; + + @FindBy(xpath = "//button[contains(@class, 'ant-btn')]/span[contains(., 'OK')]") + private WebElement deleteConfirmButton; + + private final CreateYarnQueueForm createYarnQueueForm = new CreateYarnQueueForm(); + + public YarnQueuePage(RemoteWebDriver driver) { + super(driver); + } + + public YarnQueuePage createYarnQueue(String queueLabel, String description) { + waitForPageLoading(); + + new WebDriverWait(driver, Constants.DEFAULT_WEBDRIVER_WAIT_DURATION) + .until(ExpectedConditions.elementToBeClickable(buttonCreateYarnQueue)); + buttonCreateYarnQueue.click(); + + createYarnQueueForm.inputQueueLabel.sendKeys(queueLabel); + createYarnQueueForm.inputDescription.sendKeys(description); + + createYarnQueueForm.buttonOk().click(); + return this; + } + + public YarnQueuePage editYarnQueue(String queueLabel, String editQueueLabel, String description) { + waitForPageLoading(); + + yarnQueueList().stream() + .filter(it -> it.getText().contains(queueLabel)) + .flatMap( + it -> it.findElements(By.xpath(".//button[contains(@tooltip, 'Edit')]")).stream()) + .filter(WebElement::isDisplayed) + .findFirst() + .orElseThrow(() -> new RuntimeException("No edit button in yarn queue list")) + .click(); + + new WebDriverWait(driver, Constants.DEFAULT_WEBDRIVER_WAIT_DURATION) + .until(ExpectedConditions.elementToBeClickable(createYarnQueueForm.buttonOk)); + createYarnQueueForm.inputQueueLabel().clear(); + createYarnQueueForm.inputQueueLabel().sendKeys(editQueueLabel); + createYarnQueueForm.inputDescription().clear(); + createYarnQueueForm.inputDescription().sendKeys(description); + + createYarnQueueForm.buttonOk().click(); + return this; + } + + public YarnQueuePage deleteYarnQueue(String queueLabel) { + waitForPageLoading(); + + yarnQueueList().stream() + .filter(it -> it.getText().contains(queueLabel)) + .flatMap( + it -> it.findElements(By.xpath(".//button[contains(@tooltip, 'Delete')]")).stream()) + .filter(WebElement::isDisplayed) + .findFirst() + .orElseThrow(() -> new RuntimeException("No delete button in yarn queue list")) + .click(); + + new WebDriverWait(driver, Constants.DEFAULT_WEBDRIVER_WAIT_DURATION) + .until(ExpectedConditions.elementToBeClickable(deleteConfirmButton)); + + deleteConfirmButton.click(); + + return this; + } + + private void waitForPageLoading() { + new WebDriverWait(driver, Constants.DEFAULT_WEBDRIVER_WAIT_DURATION) + .until(ExpectedConditions.urlContains("/setting/yarn-queue")); + } + + @Getter + public class CreateYarnQueueForm { + + CreateYarnQueueForm() { + PageFactory.initElements(driver, this); + } + + @FindBy(id = "YarnQueueEditForm_queueLabel") + private WebElement inputQueueLabel; + + @FindBy(id = "YarnQueueEditForm_description") + private WebElement inputDescription; + + @FindBy(xpath = "//button[contains(@class, 'ant-btn')]//span[contains(text(), 'OK')]") + private WebElement buttonOk; + + @FindBy(xpath = "//button[contains(@class, 'ant-btn')]//span[contains(text(), 'Cancel')]") + private WebElement buttonCancel; + } +}