Browse Source

Add end-to-end test framework and some basic cases (#6419)

3.0.0/version-upgrade
kezhenxu94 3 years ago committed by GitHub
parent
commit
251255009a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 68
      .github/workflows/e2e.yml
  2. 42
      docker/build/hooks/build
  3. 98
      dolphinscheduler-e2e/README.md
  4. 40
      dolphinscheduler-e2e/dolphinscheduler-e2e-case/pom.xml
  5. 98
      dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/security/TenantE2ETest.java
  6. 47
      dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java
  7. 78
      dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/TenantPage.java
  8. 35
      dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/docker/tenant/docker-compose.yaml
  9. 32
      dolphinscheduler-e2e/dolphinscheduler-e2e-core/pom.xml
  10. 41
      dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinScheduler.java
  11. 187
      dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java
  12. 55
      dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/TestDescription.java
  13. 31
      dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/resources/log4j2.xml
  14. 20
      dolphinscheduler-e2e/lombok.config
  15. 138
      dolphinscheduler-e2e/pom.xml
  16. 2
      dolphinscheduler-standalone-server/src/main/java/org/apache/dolphinscheduler/server/StandaloneServer.java
  17. 5
      dolphinscheduler-ui/src/js/conf/home/pages/security/pages/tenement/_source/createTenement.vue
  18. 4
      dolphinscheduler-ui/src/js/conf/home/pages/security/pages/tenement/_source/list.vue
  19. 2
      dolphinscheduler-ui/src/js/conf/home/pages/security/pages/tenement/index.vue
  20. 4
      dolphinscheduler-ui/src/js/conf/login/App.vue
  21. 12
      dolphinscheduler-ui/src/js/module/components/popup/popover.vue

68
.github/workflows/e2e.yml

@ -15,60 +15,54 @@
# limitations under the License.
#
on: ["pull_request"]
on:
pull_request:
push:
branches:
- e2e
env:
DOCKER_DIR: ./docker
LOG_DIR: /tmp/dolphinscheduler
TAG: ci
RECORDING_PATH: /tmp/recording
name: Test
name: E2E
concurrency:
group: e2e-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test:
name: E2E
e2e:
name: ${{ matrix.case.name }}
runs-on: ubuntu-latest
strategy:
matrix:
case:
- name: Tenant
class: org.apache.dolphinscheduler.e2e.cases.security.TenantE2ETest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Sanity Check
uses: ./.github/actions/sanity-check
- uses: actions/cache@v1
- name: Cache local Maven repository
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
- name: Build Image
run: TAG=ci sh ./docker/build/hooks/build
- name: Run Test
run: |
sh ./docker/build/hooks/build
- name: Docker Run
run: |
export VERSION=$(cat $(pwd)/pom.xml | grep '<revision>' -m 1 | awk '{print $1}' | sed 's/<revision>//' | sed 's/<\/revision>//')
sed -i "s/apache\/dolphinscheduler:latest/apache\/dolphinscheduler:${VERSION}/g" $(pwd)/docker/docker-swarm/docker-compose.yml
docker-compose -f $(pwd)/docker/docker-swarm/docker-compose.yml up -d
- name: Check Server Status
run: sh $(pwd)/docker/docker-swarm/check
- name: Prepare e2e env
run: |
sudo apt-get install -y libxss1 libappindicator1 libindicator7 xvfb unzip libgbm1
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo dpkg -i google-chrome*.deb
sudo apt-get install -f -y
google-chrome -version
googleVersion=$(curl -s https://chromedriver.storage.googleapis.com/LATEST_RELEASE)
wget -N https://chromedriver.storage.googleapis.com/${googleVersion}/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
sudo mv -f chromedriver /usr/local/share/chromedriver
sudo ln -s /usr/local/share/chromedriver /usr/local/bin/chromedriver
# - name: Run e2e Test
# run: cd ./e2e && mvn -B clean test
- name: Collect logs
if: failure()
uses: actions/upload-artifact@v2
./mvnw -f dolphinscheduler-e2e/pom.xml -am \
-DfailIfNoTests=false \
-Dtest=${{ matrix.case.class }} test
- uses: actions/upload-artifact@v2
if: always()
name: Upload Recording
with:
name: dslogs
path: ${{ github.workspace }}/docker/docker-swarm/dolphinscheduler-logs
name: recording
path: ${{ env.RECORDING_PATH }}
retention-days: 1

42
docker/build/hooks/build

@ -18,41 +18,17 @@
set -e
echo "------ dolphinscheduler start - build -------"
printenv
ROOT_DIR=$(dirname "$0")/../../..
MVN="$ROOT_DIR"/mvnw
VERSION=$("$MVN" -q -DforceStdout -N org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate -Dexpression=project.version)
if [ -z "${VERSION}" ]
then
echo "set default environment variable [VERSION]"
export VERSION=$(cat $(pwd)/pom.xml | grep '<version>' -m 1 | awk '{print $1}' | sed 's/<version>//' | sed 's/<\/version>//')
fi
DOCKER_REPO=${DOCKER_REPO:-"apache/dolphinscheduler"}
TAG=${TAG:-"$VERSION"}
if [ "${DOCKER_REPO}x" = "x" ]
then
echo "set default environment variable [DOCKER_REPO]"
export DOCKER_REPO='apache/dolphinscheduler'
fi
echo "Building Docker image as: $DOCKER_REPO:$TAG"
echo "Version: $VERSION"
echo "Repo: $DOCKER_REPO"
"$MVN" -B clean package -Prelease -Dmaven.test.skip=true -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
echo -e "Current Directory is $(pwd)\n"
cp "$ROOT_DIR"/dolphinscheduler-dist/target/apache-dolphinscheduler-$VERSION-bin.tar.gz "$ROOT_DIR"/docker/build/
# maven package(Project Directory)
echo -e "./mvnw -B clean package -Prelease -Dmaven.test.skip=true -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120"
./mvnw -B clean package -Prelease -Dmaven.test.skip=true -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120
# mv dolphinscheduler-bin.tar.gz file to docker/build directory
echo -e "mv $(pwd)/dolphinscheduler-dist/target/apache-dolphinscheduler-${VERSION}-bin.tar.gz $(pwd)/docker/build/\n"
mv $(pwd)/dolphinscheduler-dist/target/apache-dolphinscheduler-${VERSION}-bin.tar.gz $(pwd)/docker/build/
# docker build
BUILD_COMMAND="docker build --build-arg VERSION=${VERSION} -t $DOCKER_REPO:${VERSION} $(pwd)/docker/build/"
echo -e "$BUILD_COMMAND\n"
if (docker info 2> /dev/null | grep -i "ERROR"); then
sudo $BUILD_COMMAND
else
$BUILD_COMMAND
fi
echo "------ dolphinscheduler end - build -------"
docker build --build-arg VERSION=$VERSION -t $DOCKER_REPO:$TAG "$ROOT_DIR"/docker/build/

98
dolphinscheduler-e2e/README.md

@ -0,0 +1,98 @@
# DolphinScheduler End-to-End Test
## Page Object Model
DolphinScheduler End-to-End test respects
the [Page Object Model (POM)](https://www.selenium.dev/documentation/guidelines/page_object_models/) design pattern.
Every page of DolphinScheduler is abstracted into a class for better maintainability.
### Example
The login page is abstracted
as [`LoginPage`](dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java), with the
following fields,
```java
public final class LoginPage {
@FindBy(id = "input-username")
private WebElement inputUsername;
@FindBy(id = "input-password")
private WebElement inputPassword;
@FindBy(id = "button-login")
private WebElement buttonLogin;
}
```
where `inputUsername`, `inputPassword` and `buttonLogin` are the main elements on UI that we are interested in. They are
annotated with `FindBy` so that the test framework knows how to locate the elements on UI. You can locate the elements
by `id`, `className`, `css` selector, `tagName`, or even `xpath`, please refer
to [the JavaDoc](https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/support/FindBy.html).
**Note:** for better maintainability, it's essential to add some convenient `id` or `class` on UI for the wanted
elements if needed, avoid using too complex `xpath` selector or `css` selector that is not maintainable when UI have
styles changes.
With those fields declared, we should also initialize them with a web driver. Here we pass the web driver into the
constructor and invoke `PageFactory.initElements` to initialize those fields,
```java
public final class LoginPage {
// ...
public LoginPage(RemoteWebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
}
```
then, all those UI elements are properly filled in.
## Test Environment Setup
DolphinScheduler End-to-End test uses [testcontainers](https://www.testcontainers.org) to set up the testing
environment, with docker compose.
Typically, every test case needs one or more `docker-compose.yaml` files to set up all needed components, and expose the
DolphinScheduler UI port for testing. You can use `@DolphinScheduler(composeFiles = "")` and pass
the `docker-compose.yaml` files to automatically set up the environment in the test class.
```java
@DolphinScheduler(composeFiles = "docker/tenant/docker-compose.yaml")
class TenantE2ETest {
}
```
You can get the web driver that is ready for testing in the class by adding a field of type `RemoteWebDriver`, which
will be automatically injected via the testing framework.
```java
@DolphinScheduler(composeFiles = "docker/tenant/docker-compose.yaml")
class TenantE2ETest {
private RemoteWebDriver browser;
}
```
Then the field `browser` can be used in the test methods.
```java
@DolphinScheduler(composeFiles = "docker/tenant/docker-compose.yaml")
class TenantE2ETest {
private RemoteWebDriver browser;
@Test
void testLogin() {
final LoginPage page = new LoginPage(browser); // <<-- use the browser injected
}
}
```
## Notes
- For UI tests, it's common that the pages might need some time to load, or the operations might need some time to
complete, we can use `await().untilAsserted(() -> {})` to wait for the assertions.

40
dolphinscheduler-e2e/dolphinscheduler-e2e-case/pom.xml

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to 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. Apache Software Foundation (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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dolphinscheduler-e2e</artifactId>
<groupId>org.apache.dolphinscheduler</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dolphinscheduler-e2e-case</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.dolphinscheduler</groupId>
<artifactId>dolphinscheduler-e2e-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

98
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/cases/security/TenantE2ETest.java

@ -0,0 +1,98 @@
/*
* Licensed to 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. Apache Software Foundation (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.dolphinscheduler.e2e.cases.security;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import org.apache.dolphinscheduler.e2e.core.DolphinScheduler;
import org.apache.dolphinscheduler.e2e.pages.LoginPage;
import org.apache.dolphinscheduler.e2e.pages.TenantPage;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
@DolphinScheduler(composeFiles = "docker/tenant/docker-compose.yaml")
class TenantE2ETest {
private RemoteWebDriver browser;
@Test
@Order(1)
void testLogin() {
final LoginPage page = new LoginPage(browser);
page.inputUsername().sendKeys("admin");
page.inputPassword().sendKeys("dolphinscheduler123");
page.buttonLogin().click();
}
@Test
@Order(10)
void testCreateTenant() {
final TenantPage page = new TenantPage(browser);
final String tenant = System.getProperty("user.name");
page.buttonCreateTenant().click();
page.createTenantForm().inputTenantCode().sendKeys(tenant);
page.createTenantForm().inputDescription().sendKeys("Test");
page.createTenantForm().buttonSubmit().click();
await().untilAsserted(() -> assertThat(page.tenantList())
.as("Tenant list should contain newly-created tenant")
.extracting(WebElement::getText)
.anyMatch(it -> it.contains(tenant)));
}
@Test
@Order(20)
void testCreateDuplicateTenant() {
final String tenant = System.getProperty("user.name");
final TenantPage page = new TenantPage(browser);
page.buttonCreateTenant().click();
page.createTenantForm().inputTenantCode().sendKeys(tenant);
page.createTenantForm().inputDescription().sendKeys("Test");
page.createTenantForm().buttonSubmit().click();
await().untilAsserted(() -> assertThat(browser.findElementByTagName("body")
.getText().contains("already exists"))
.as("Should fail when creating a duplicate tenant")
.isTrue());
page.createTenantForm().buttonCancel().click();
}
@Test
@Order(30)
void testDeleteTenant() {
final String tenant = System.getProperty("user.name");
final TenantPage page = new TenantPage(browser);
page.tenantList()
.stream()
.filter(it -> it.getText().contains(tenant))
.findFirst()
.ifPresent(it -> it.findElement(By.className("delete")).click());
page.buttonConfirm().click();
}
}

47
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/LoginPage.java

@ -0,0 +1,47 @@
/*
* Licensed to 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. Apache Software Foundation (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.dolphinscheduler.e2e.pages;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import lombok.Getter;
@Getter
public final class LoginPage {
private final RemoteWebDriver driver;
@FindBy(id = "input-username")
private WebElement inputUsername;
@FindBy(id = "input-password")
private WebElement inputPassword;
@FindBy(id = "button-login")
private WebElement buttonLogin;
public LoginPage(RemoteWebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
}

78
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/java/org/apache/dolphinscheduler/e2e/pages/TenantPage.java

@ -0,0 +1,78 @@
/*
* Licensed to 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. Apache Software Foundation (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.dolphinscheduler.e2e.pages;
import java.util.List;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.FindBys;
import org.openqa.selenium.support.PageFactory;
import lombok.Getter;
@Getter
public final class TenantPage {
private final WebDriver driver;
@FindBy(id = "button-create-tenant")
private WebElement buttonCreateTenant;
@FindBy(className = "rows-tenant")
private List<WebElement> tenantList;
@FindBys({
@FindBy(className = "el-popconfirm"),
@FindBy(className = "el-button--primary"),
})
private WebElement buttonConfirm;
private final CreateTenantForm createTenantForm;
public TenantPage(WebDriver driver) {
this.driver = driver;
this.createTenantForm = new CreateTenantForm();
PageFactory.initElements(driver, this);
}
@Getter
public class CreateTenantForm {
CreateTenantForm() {
PageFactory.initElements(driver, this);
}
@FindBy(id = "input-tenant-code")
private WebElement inputTenantCode;
@FindBy(id = "select-queue")
private WebElement selectQueue;
@FindBy(id = "input-description")
private WebElement inputDescription;
@FindBy(id = "button-submit")
private WebElement buttonSubmit;
@FindBy(id = "button-cancel")
private WebElement buttonCancel;
}
}

35
dolphinscheduler-e2e/dolphinscheduler-e2e-case/src/test/resources/docker/tenant/docker-compose.yaml

@ -0,0 +1,35 @@
#
# 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.
#
version: "2.1"
services:
dolphinscheduler:
image: apache/dolphinscheduler:ci
command: [ standalone-server ]
expose:
- 12345
networks:
- e2e
healthcheck:
test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/12345" ]
interval: 5s
timeout: 60s
retries: 120
networks:
e2e:

32
dolphinscheduler-e2e/dolphinscheduler-e2e-core/pom.xml

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to 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. Apache Software Foundation (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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dolphinscheduler-e2e</artifactId>
<groupId>org.apache.dolphinscheduler</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dolphinscheduler-e2e-core</artifactId>
</project>

41
dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinScheduler.java

@ -0,0 +1,41 @@
/*
* Licensed to 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. Apache Software Foundation (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.dolphinscheduler.e2e.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.junit.jupiter.Testcontainers;
@Inherited
@Testcontainers
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@TestMethodOrder(OrderAnnotation.class)
@ExtendWith(DolphinSchedulerExtension.class)
public @interface DolphinScheduler {
String[] composeFiles();
}

187
dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/DolphinSchedulerExtension.java

@ -0,0 +1,187 @@
/*
* Licensed to 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. Apache Software Foundation (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.dolphinscheduler.e2e.core;
import static org.testcontainers.containers.BrowserWebDriverContainer.VncRecordingMode.RECORD_ALL;
import static org.testcontainers.containers.VncRecordingContainer.VncRecordingFormat.MP4;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.testcontainers.containers.BrowserWebDriverContainer;
import org.testcontainers.containers.ContainerState;
import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.shaded.org.apache.commons.lang.SystemUtils;
import org.testcontainers.shaded.org.awaitility.Awaitility;
import com.google.common.base.Strings;
import com.google.common.net.HostAndPort;
import lombok.extern.slf4j.Slf4j;
@Slf4j
final class DolphinSchedulerExtension
implements BeforeAllCallback, AfterAllCallback,
BeforeEachCallback {
private final boolean LOCAL_MODE = Objects.equals(System.getProperty("local"), "true");
private RemoteWebDriver driver;
private DockerComposeContainer<?> compose;
private BrowserWebDriverContainer<?> browser;
@Override
@SuppressWarnings("UnstableApiUsage")
public void beforeAll(ExtensionContext context) throws IOException {
Awaitility.setDefaultTimeout(Duration.ofSeconds(5));
Awaitility.setDefaultPollInterval(Duration.ofSeconds(1));
Network network = null;
HostAndPort address = null;
String rootPath = "/";
if (!LOCAL_MODE) {
compose = createDockerCompose(context);
compose.start();
final ContainerState dsContainer = compose.getContainerByServiceName("dolphinscheduler_1")
.orElseThrow(() -> new RuntimeException("Failed to find a container named 'dolphinscheduler'"));
final String networkId = dsContainer.getContainerInfo().getNetworkSettings().getNetworks().keySet().iterator().next();
network = new Network() {
@Override
public String getId() {
return networkId;
}
@Override
public void close() {
}
@Override
public Statement apply(Statement base, Description description) {
return null;
}
};
address = HostAndPort.fromParts("dolphinscheduler", 12345);
rootPath = "/dolphinscheduler";
}
final Path record;
if (!Strings.isNullOrEmpty(System.getenv("RECORDING_PATH"))) {
record = Paths.get(System.getenv("RECORDING_PATH"));
if (!record.toFile().exists()) {
if (!record.toFile().mkdir()) {
throw new IOException("Failed to create recording directory: " + record.toAbsolutePath());
}
}
} else {
record = Files.createTempDirectory("record-");
}
browser = new BrowserWebDriverContainer<>()
.withCapabilities(new ChromeOptions())
.withRecordingMode(RECORD_ALL, record.toFile(), MP4);
if (network != null) {
browser.withNetwork(network);
}
browser.start();
driver = browser.getWebDriver();
driver.manage().timeouts()
.implicitlyWait(5, TimeUnit.SECONDS)
.pageLoadTimeout(5, TimeUnit.SECONDS);
if (address == null) {
try {
address = HostAndPort.fromParts(browser.getTestHostIpAddress(), 8888);
} catch (UnsupportedOperationException ignored) {
if (SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_MAC_OSX) {
address = HostAndPort.fromParts("host.docker.internal", 8888);
}
}
}
if (address == null) {
throw new UnsupportedOperationException("Unsupported operation system");
}
driver.get(new URL("http", address.getHost(), address.getPort(), rootPath).toString());
browser.beforeTest(new TestDescription(context));
}
@Override
public void afterAll(ExtensionContext context) {
browser.afterTest(new TestDescription(context), Optional.empty());
browser.stop();
if (compose != null) {
compose.stop();
}
}
@Override
public void beforeEach(ExtensionContext context) {
final Object instance = context.getRequiredTestInstance();
Stream.of(instance.getClass().getDeclaredFields())
.filter(f -> WebDriver.class.isAssignableFrom(f.getType()))
.forEach(it -> {
try {
it.setAccessible(true);
it.set(instance, driver);
} catch (IllegalAccessException e) {
LOGGER.error("Failed to inject web driver to field: {}", it.getName(), e);
}
});
}
private DockerComposeContainer<?> createDockerCompose(ExtensionContext context) {
final Class<?> clazz = context.getRequiredTestClass();
final DolphinScheduler annotation = clazz.getAnnotation(DolphinScheduler.class);
final List<File> files = Stream.of(annotation.composeFiles())
.map(it -> DolphinScheduler.class.getClassLoader().getResource(it))
.filter(Objects::nonNull)
.map(URL::getPath)
.map(File::new)
.collect(Collectors.toList());
compose = new DockerComposeContainer<>(files)
.withPull(true)
.withTailChildContainers(true)
.waitingFor("dolphinscheduler_1", Wait.forHealthcheck());
return compose;
}
}

55
dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/java/org/apache/dolphinscheduler/e2e/core/TestDescription.java

@ -0,0 +1,55 @@
/*
* Licensed to 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. Apache Software Foundation (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.dolphinscheduler.e2e.core;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.platform.commons.util.StringUtils.isBlank;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import org.junit.jupiter.api.extension.ExtensionContext;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
final class TestDescription implements org.testcontainers.lifecycle.TestDescription {
private static final String UNKNOWN_NAME = "unknown";
private final ExtensionContext context;
@Override
public String getTestId() {
return context.getUniqueId();
}
@Override
public String getFilesystemFriendlyName() {
final String contextId = context.getUniqueId();
try {
return (isBlank(contextId))
? UNKNOWN_NAME
: URLEncoder.encode(contextId, UTF_8.toString());
} catch (UnsupportedEncodingException e) {
return UNKNOWN_NAME;
}
}
}

31
dolphinscheduler-e2e/dolphinscheduler-e2e-core/src/main/resources/log4j2.xml

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
-->
<Configuration status="DEBUG">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout charset="UTF-8" pattern="%d %c %L [%t] %-5p %x - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

20
dolphinscheduler-e2e/lombok.config

@ -0,0 +1,20 @@
#
# 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.
#
lombok.accessors.fluent=true
lombok.log.fieldname=LOGGER
lombok.accessors.fluent=true

138
dolphinscheduler-e2e/pom.xml

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.dolphinscheduler</groupId>
<artifactId>dolphinscheduler-e2e</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>dolphinscheduler-e2e-core</module>
<module>dolphinscheduler-e2e-case</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.7.2</junit.version>
<selenium.version>3.141.59</selenium.version>
<lombok.version>1.18.20</lombok.version>
<assertj-core.version>3.20.2</assertj-core.version>
<awaitility.version>4.1.0</awaitility.version>
<kotlin.version>1.5.30</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>selenium</artifactId>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-chrome-driver</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-support</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj-core.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>${awaitility.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>1.16.0</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
</project>

2
dolphinscheduler-standalone-server/src/main/java/org/apache/dolphinscheduler/server/StandaloneServer.java

@ -125,6 +125,8 @@ public class StandaloneServer {
if (Files.exists(taskPluginPath)) {
System.setProperty("task.plugin.binding", taskPluginPath.toString());
System.setProperty("task.plugin.dir", "");
} else {
System.setProperty("task.plugin.binding", "lib/plugin/task/shell");
}
}
}

5
dolphinscheduler-ui/src/js/conf/home/pages/security/pages/tenement/_source/createTenement.vue

@ -16,6 +16,8 @@
*/
<template>
<m-popover
okId="button-submit"
cancelId="button-cancel"
ref="popover"
:ok-text="item ? $t('Edit') : $t('Submit')"
@ok="_ok"
@ -26,6 +28,7 @@
<template slot="name"><strong>*</strong>{{$t('OS Tenant Code')}}</template>
<template slot="content">
<el-input
id="input-tenant-code"
type="input"
:disabled="item ? true : false"
v-model="tenantCode"
@ -40,6 +43,7 @@
<template slot="content">
<el-select v-model="queueId" size="small">
<el-option
id="select-queue"
v-for="city in queueList"
:key="city.id"
:value="city.id"
@ -52,6 +56,7 @@
<template slot="name">{{$t('Description')}}</template>
<template slot="content">
<el-input
id="input-description"
type="textarea"
v-model="description"
size="small"

4
dolphinscheduler-ui/src/js/conf/home/pages/security/pages/tenement/_source/list.vue

@ -17,7 +17,7 @@
<template>
<div class="list-model">
<div class="table-box">
<el-table :data="list" size="mini" style="width: 100%">
<el-table :data="list" size="mini" style="width: 100%" row-class-name="rows-tenant">
<el-table-column type="index" :label="$t('#')" width="50"></el-table-column>
<el-table-column prop="tenantCode" :label="$t('OS Tenant Code')" min-width="100"></el-table-column>
<el-table-column :label="$t('Description')" min-width="100">
@ -51,7 +51,7 @@
:title="$t('Delete?')"
@onConfirm="_delete(scope.row,scope.row.id)"
>
<el-button type="danger" size="mini" icon="el-icon-delete" circle slot="reference"></el-button>
<el-button type="danger" size="mini" icon="el-icon-delete" circle slot="reference" class="delete"></el-button>
</el-popconfirm>
</el-tooltip>
</template>

2
dolphinscheduler-ui/src/js/conf/home/pages/security/pages/tenement/index.vue

@ -19,7 +19,7 @@
<template slot="conditions">
<m-conditions @on-conditions="_onConditions">
<template slot="button-group" v-if="isADMIN">
<el-button size="mini" @click="_create('')">{{$t('Create Tenant')}}</el-button>
<el-button id="button-create-tenant" size="mini" @click="_create('')">{{$t('Create Tenant')}}</el-button>
<el-dialog
:title="item ? $t('Edit Tenant') : $t('Create Tenant')"
v-if="createTenementDialog"

4
dolphinscheduler-ui/src/js/conf/login/App.vue

@ -24,6 +24,7 @@
<label>{{$t('User Name')}}</label>
<div>
<el-input
id="input-username"
type="text"
v-model.trim="userName"
:placeholder="$t('Please enter user name')"
@ -39,6 +40,7 @@
<label>{{$t('Password')}}</label>
<div>
<el-input
id="input-password"
type="password"
v-model="userPassword"
:placeholder="$t('Please enter your password')"
@ -51,7 +53,7 @@
</p>
</div>
<div class="list" style="margin-top: 10px;">
<el-button style="width: 365px" type="primary" round :loading="spinnerLoading" long @click="_ok">{{spinnerLoading ? $t('Loading...') : ` ${$t('Login')} `}} </el-button>
<el-button id="button-login" style="width: 365px" type="primary" round :loading="spinnerLoading" long @click="_ok">{{spinnerLoading ? $t('Loading...') : ` ${$t('Login')} `}} </el-button>
</div>
</div>
</div>

12
dolphinscheduler-ui/src/js/module/components/popup/popover.vue

@ -20,8 +20,8 @@
<slot name="content"></slot>
</div>
<div class="bottom-p">
<el-button type="text" size="mini" round @click="close()" :disabled="disabled"> {{$t('Cancel')}} </el-button>
<el-button type="primary" size="mini" round :loading="spinnerLoading" @click="ok()" :disabled="disabled || apDisabled">{{spinnerLoading ? $t('Loading...') : okText}} </el-button>
<el-button :id="cancelId" type="text" size="mini" round @click="close()" :disabled="disabled"> {{$t('Cancel')}} </el-button>
<el-button :id="okId" type="primary" size="mini" round :loading="spinnerLoading" @click="ok()" :disabled="disabled || apDisabled">{{spinnerLoading ? $t('Loading...') : okText}} </el-button>
</div>
</div>
</template>
@ -47,6 +47,14 @@
asynLoading: {
type: Boolean,
default: false
},
cancelId: {
type: String,
default: ''
},
okId: {
type: String,
default: ''
}
},
methods: {

Loading…
Cancel
Save