kezhenxu94
3 years ago
committed by
GitHub
21 changed files with 963 additions and 76 deletions
@ -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. |
@ -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> |
@ -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(); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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: |
@ -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> |
@ -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(); |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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> |
@ -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 |
@ -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> |
Loading…
Reference in new issue