You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by ra...@apache.org on 2021/02/23 08:26:54 UTC

[dubbo-admin] branch develop updated: Add more tests for zookeeper registry (#696)

This is an automated email from the ASF dual-hosted git repository.

ranke pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git


The following commit(s) were added to refs/heads/develop by this push:
     new d25b649  Add more tests for zookeeper registry (#696)
d25b649 is described below

commit d25b6492f5285c49dd360604909904c6ecb5b542
Author: Huang YunKun <ht...@gmail.com>
AuthorDate: Tue Feb 23 16:26:27 2021 +0800

    Add more tests for zookeeper registry (#696)
    
    * fix chinese word issue in screenshot
    
    * add test coverage for integration
    support chinese for integration screenshot
    
    * add some unit test
    
    * add more ui test
    
    * add await for resul showing
    
    * use internal screenshot
    
    * add png for screenshot
---
 .codecov.yml                                       |  18 +++
 .github/workflows/ci.yml                           |   3 +-
 .github/workflows/integration.yml                  |  25 +++-
 README.md                                          |   2 +-
 README_ZH.md                                       |   2 +-
 docker/latest/Dockerfile                           |   6 +-
 docker/latest/entrypoint.sh                        |   4 +
 .../src/main/resources/application-test.properties |   6 +-
 .../src/main/resources/application.properties      |   6 +-
 .../dubbo/admin/utils/LocalDateTimeUtilTest.java   |  49 +++++++
 dubbo-admin-test/pom.xml                           |  13 +-
 .../test/java/org/apache/dubbo/admin/BaseIT.java   |  51 +++++--
 .../test/java/org/apache/dubbo/admin/LoginIT.java  |  47 +------
 .../dubbo/admin/{BaseIT.java => ManageIT.java}     |  33 ++---
 .../dubbo/admin/{BaseIT.java => MetricIT.java}     |  32 ++---
 .../java/org/apache/dubbo/admin/ServiceIT.java     |  57 ++++++++
 .../admin/{BaseIT.java => pages/BasePage.java}     |  28 +---
 .../org/apache/dubbo/admin/pages/LoginPage.java    |  57 ++++++++
 .../org/apache/dubbo/admin/pages/ManagePage.java   |  57 ++++++++
 .../{BaseIT.java => pages/MetricRelationPage.java} |  32 +----
 .../org/apache/dubbo/admin/pages/ServicePage.java  | 152 +++++++++++++++++++++
 dubbo-admin-ui/src/components/test/TestMethod.vue  |   2 +-
 22 files changed, 522 insertions(+), 160 deletions(-)

diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644
index 0000000..5ce536d
--- /dev/null
+++ b/.codecov.yml
@@ -0,0 +1,18 @@
+# Documentation: https://docs.codecov.io/docs/codecov-yaml
+
+codecov:
+  branch: develop
+
+coverage:
+  status:
+    project:
+      default:
+        branches:
+          - develop
+
+comment:
+  layout: "reach,diff,flags,files,footer"
+  behavior: default
+  require_changes: no
+  branches:
+    - develop
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 730bc8e..8cc2801 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -19,4 +19,5 @@ jobs:
         run: ./mvnw clean install --batch-mode -DskipTests=false -Dcheckstyle.skip=false -Drat.skip=false -Dmaven.javadoc.skip=true
       - name: Upload coverage to codecov
         uses: codecov/codecov-action@v1
-
+        with:
+          flags: unit
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 54c2ab6..9d74ffa 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -12,13 +12,34 @@ jobs:
         run: cp docker/latest/** ./
       - name: Docker build
         run: docker build -t apache/dubbo-admin-integration:latest .
+      - name: Prepare agent
+        run: mkdir jacoco && wget https://repo1.maven.org/maven2/org/jacoco/org.jacoco.agent/0.8.6/org.jacoco.agent-0.8.6-runtime.jar -O jacoco/jacoco.jar && wget https://repo1.maven.org/maven2/org/jacoco/org.jacoco.cli/0.8.6/org.jacoco.cli-0.8.6-nodeps.jar -O jacoco/jacoco-cli.jar
       - name: Build provider and consumer image
         run: ./mvnw --batch-mode --projects dubbo-admin-test --activate-profiles build-provider package && ./mvnw --batch-mode --projects dubbo-admin-test --activate-profiles build-consumer package
       - name: Setup xvfb
-        run: sudo apt-get install xvfb
+        run: sudo apt-get install xvfb ttf-wqy-zenhei -y && fc-cache -v
       - name: Run integration test
         run: xvfb-run --server-args="-screen 0, 1024x768x24" ./mvnw --batch-mode --projects dubbo-admin-test --activate-profiles dubbo-admin-integration-test -Ddocker.showLogs=true docker:stop docker:remove verify
+      - name: Save failure screenshots
+        if: ${{ failure() }}
+        uses: actions/upload-artifact@v2
+        with:
+          name: failure-screenshots
+          path: dubbo-admin-test/target/screens/
       - uses: actions/upload-artifact@v2
         with:
           name: screenshots
-          path: dubbo-admin-test/target/screens/
\ No newline at end of file
+          path: dubbo-admin-test/target/screens/
+      - name: Set up JDK
+        uses: actions/setup-java@v1
+        with:
+          java-version: 8
+      - name: Compile server project
+        run: ./mvnw clean package -DskipTests=true -pl dubbo-admin-server
+      - name: Generate report
+        run: java -jar jacoco/jacoco-cli.jar report jacoco/jacoco.exec --classfiles dubbo-admin-server/target/classes/ --sourcefiles dubbo-admin-server/src --xml jacoco/jacoco.xml
+      - name: Upload coverage to codecov
+        uses: codecov/codecov-action@v1
+        with:
+          flags: integration
+          directory: ./jacoco
diff --git a/README.md b/README.md
index 4e84154..37be75b 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # Dubbo Admin
 
 ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/apache/dubbo-admin/CI)
-[![codecov](https://codecov.io/gh/apache/dubbo-admin/branch/develop/graph/badge.svg)](https://codecov.io/gh/apache/dubbo-admin)
+[![codecov](https://codecov.io/gh/apache/dubbo-admin/branch/develop/graph/badge.svg)](https://codecov.io/gh/apache/dubbo-admin/branches/develop)
 ![license](https://img.shields.io/github/license/apache/dubbo-admin.svg)
 [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/apache/dubbo-admin.svg)](http://isitmaintained.com/project/apache/dubbo-admin "Average time to resolve an issue")
 [![Percentage of issues still open](http://isitmaintained.com/badge/open/apache/dubbo-admin.svg)](http://isitmaintained.com/project/apache/dubbo-admin "Percentage of issues still open")
diff --git a/README_ZH.md b/README_ZH.md
index c49700a..f4f0d90 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -1,7 +1,7 @@
 # Dubbo控制台
 
 [![Build Status](https://travis-ci.org/apache/dubbo-admin.svg?branch=develop)](https://travis-ci.org/apache/dubbo-admin)
-[![codecov](https://codecov.io/gh/apache/dubbo-admin/branch/develop/graph/badge.svg)](https://codecov.io/gh/apache/dubbo-admin)
+[![codecov](https://codecov.io/gh/apache/dubbo-admin/branch/develop/graph/badge.svg)](https://codecov.io/gh/apache/dubbo-admin/branches/develop)
 ![license](https://img.shields.io/github/license/apache/dubbo-admin.svg)
 
 [English version](README.md).
diff --git a/docker/latest/Dockerfile b/docker/latest/Dockerfile
index 7fa2f81..d45040a 100644
--- a/docker/latest/Dockerfile
+++ b/docker/latest/Dockerfile
@@ -23,5 +23,9 @@ FROM openjdk:8-jre
 LABEL maintainer="dev@dubbo.apache.org"
 RUN apt-get update && apt-get install -y tini
 COPY --from=0 /source/dubbo-admin-snapshot/dubbo-admin-distribution/target/dubbo-admin-0.3.0-SNAPSHOT.jar /app.jar
-ENTRYPOINT ["tini","--","java","-XX:+UnlockExperimentalVMOptions","-XX:+UseCGroupMemoryLimitForHeap","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
+COPY entrypoint.sh /usr/local/bin/entrypoint.sh
+
+ENV JAVA_OPTS = ""
+
+ENTRYPOINT ["tini", "--", "/usr/local/bin/entrypoint.sh"]
 EXPOSE 8080
diff --git a/docker/latest/entrypoint.sh b/docker/latest/entrypoint.sh
new file mode 100755
index 0000000..36cadde
--- /dev/null
+++ b/docker/latest/entrypoint.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+
+exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar
diff --git a/dubbo-admin-server/src/main/resources/application-test.properties b/dubbo-admin-server/src/main/resources/application-test.properties
index dce770a..b2dca26 100644
--- a/dubbo-admin-server/src/main/resources/application-test.properties
+++ b/dubbo-admin-server/src/main/resources/application-test.properties
@@ -16,7 +16,7 @@
 #
 
 # centers in dubbo2.7
-admin.registry.address=zookeeper://127.0.0.1:2183
-admin.config-center=zookeeper://127.0.0.1:2183
-admin.metadata.address=zookeeper://127.0.0.1:2183
+admin.registry.address=zookeeper://127.0.0.1:2181
+admin.config-center=zookeeper://127.0.0.1:2181
+admin.metadata.address=zookeeper://127.0.0.1:2181
 admin.check.authority=false
diff --git a/dubbo-admin-server/src/main/resources/application.properties b/dubbo-admin-server/src/main/resources/application.properties
index 01e2943..38abde6 100644
--- a/dubbo-admin-server/src/main/resources/application.properties
+++ b/dubbo-admin-server/src/main/resources/application.properties
@@ -16,9 +16,9 @@
 #
 
 # centers in dubbo2.7
-admin.registry.address=zookeeper://127.0.0.1:2183
-admin.config-center=zookeeper://127.0.0.1:2183
-admin.metadata-report.address=zookeeper://127.0.0.1:2183
+admin.registry.address=zookeeper://127.0.0.1:2181
+admin.config-center=zookeeper://127.0.0.1:2181
+admin.metadata-report.address=zookeeper://127.0.0.1:2181
 
 #admin.registry.address=nacos://127.0.0.1:8848
 #admin.config-center=nacos://127.0.0.1:8848
diff --git a/dubbo-admin-server/src/test/java/org/apache/dubbo/admin/utils/LocalDateTimeUtilTest.java b/dubbo-admin-server/src/test/java/org/apache/dubbo/admin/utils/LocalDateTimeUtilTest.java
new file mode 100644
index 0000000..6df1bf1
--- /dev/null
+++ b/dubbo-admin-server/src/test/java/org/apache/dubbo/admin/utils/LocalDateTimeUtilTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dubbo.admin.utils;
+
+import org.junit.Test;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class LocalDateTimeUtilTest {
+    @Test
+    public void shouldGetLocalDateTime() {
+        LocalDateTime localDateTime = LocalDateTimeUtil.formatToLDT("2020-01-02 10:11:12");
+
+        assertThat(localDateTime.getYear(), is(2020));
+        assertThat(localDateTime.getMonthValue(), is(1));
+        assertThat(localDateTime.getDayOfMonth(), is(2));
+
+        assertThat(localDateTime.getHour(), is(10));
+        assertThat(localDateTime.getMinute(), is(11));
+        assertThat(localDateTime.getSecond(), is(12));
+    }
+
+    @Test
+    public void shouldGetLocalDate() {
+        LocalDate localDate = LocalDateTimeUtil.formatToLD("2020-01-02");
+
+        assertThat(localDate.getYear(), is(2020));
+        assertThat(localDate.getMonthValue(), is(1));
+        assertThat(localDate.getDayOfMonth(), is(2));
+    }
+}
diff --git a/dubbo-admin-test/pom.xml b/dubbo-admin-test/pom.xml
index ccd111a..ab76dae 100644
--- a/dubbo-admin-test/pom.xml
+++ b/dubbo-admin-test/pom.xml
@@ -122,9 +122,9 @@
         </dependency>
 
         <dependency>
-            <groupId>org.seleniumhq.selenium.fluent</groupId>
-            <artifactId>fluent-selenium</artifactId>
-            <version>1.17</version>
+            <groupId>org.fluentlenium</groupId>
+            <artifactId>fluentlenium-junit</artifactId>
+            <version>3.9.1</version>
             <scope>test</scope>
         </dependency>
 
@@ -235,7 +235,13 @@
                                             </admin.config-center>
                                             <admin.metadata-report.address>zookeeper://zookeeper:2181
                                             </admin.metadata-report.address>
+                                            <JAVA_OPTS>-javaagent:/jacoco/jacoco.jar=destfile=/jacoco/jacoco.exec</JAVA_OPTS>
                                         </env>
+                                        <volumes>
+                                            <bind>
+                                                <volume>${project.basedir}/../jacoco:/jacoco</volume>
+                                            </bind>
+                                        </volumes>
                                         <links>
                                             <link>zookeeper:zookeeper</link>
                                         </links>
@@ -246,6 +252,7 @@
                                                 <method>GET</method>
                                                 <status>200</status>
                                             </http>
+                                            <time>20000</time>
                                         </wait>
                                     </run>
                                 </image>
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
index cfe11c3..7ab56b7 100644
--- a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
@@ -18,30 +18,53 @@
  */
 package org.apache.dubbo.admin;
 
-import org.apache.commons.io.FileUtils;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
+import io.github.bonigarcia.wdm.WebDriverManager;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.dubbo.admin.pages.LoginPage;
+import org.fluentlenium.adapter.junit.FluentTest;
+import org.fluentlenium.core.annotation.Page;
+import org.junit.BeforeClass;
 import org.openqa.selenium.WebDriver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.File;
-import java.io.IOException;
-
-public class BaseIT {
+public class BaseIT extends FluentTest {
     private Logger logger = LoggerFactory.getLogger(this.getClass());
 
-    public void takeShot(WebDriver webDriver, String name) {
-        TakesScreenshot scrShot = ((TakesScreenshot) webDriver);
+    protected static WebDriver driver;
+    protected static String BASE_URL;
+
+    @Page
+    LoginPage loginPage;
+
+    public BaseIT() {
+        setWebDriver("chrome");
+        setScreenshotPath("target/screens/");
+        setScreenshotMode(TriggerMode.AUTOMATIC_ON_FAIL);
+    }
+
+    @BeforeClass
+    public static void beforeClass() {
+        WebDriverManager.chromedriver().setup();
 
-        File SrcFile = scrShot.getScreenshotAs(OutputType.FILE);
+        BASE_URL = StringUtils.defaultString(System.getenv("BASEURL"), "http://localhost:8082");
+    }
+
+    @Override
+    public String getBaseUrl() {
+        return BASE_URL;
+    }
 
-        File DestFile = new File("target/screens/" + name + ".png");
+
+    public void autoLogin() {
+        goTo(loginPage);
 
         try {
-            FileUtils.copyFile(SrcFile, DestFile);
-        } catch (IOException e) {
-            logger.info("#takeShot# take shot fail", e);
+            await().untilPredicate(fluentControl -> loginPage.url().contains("login"));
+
+            loginPage.loginWithRoot();
+        } catch (Exception ignore) {
+            logger.info("already log in");
         }
     }
 }
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/LoginIT.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/LoginIT.java
index 62be596..d4bfccc 100644
--- a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/LoginIT.java
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/LoginIT.java
@@ -18,51 +18,18 @@
  */
 package org.apache.dubbo.admin;
 
-import io.github.bonigarcia.wdm.WebDriverManager;
-import org.apache.commons.lang3.StringUtils;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.apache.dubbo.admin.pages.LoginPage;
+import org.fluentlenium.core.annotation.Page;
 import org.junit.Test;
-import org.openqa.selenium.By;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.firefox.FirefoxDriver;
-import org.seleniumhq.selenium.fluent.FluentWebDriver;
-
-import java.util.concurrent.TimeUnit;
 
 public class LoginIT extends BaseIT {
-    private static WebDriver driver;
-    private static FluentWebDriver fwd;
-    private static String BASE_URL;
-
-    @BeforeClass
-    public static void beforeClass() {
-        WebDriverManager.firefoxdriver().setup();
-
-        driver = new FirefoxDriver();
-
-        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
-        fwd = new FluentWebDriver(driver);
-
-        BASE_URL = StringUtils.defaultString(System.getenv("BASEURL"), "http://localhost:8082");
-    }
-
-    @AfterClass
-    public static void afterClass() {
-        driver.quit();
-    }
-
+    @Page
+    private LoginPage loginPage;
 
     @Test
-    public void shouldOpenLogin() {
-        driver.get(BASE_URL + "/#/login");
-
-        fwd.input(By.name("username")).sendKeys("root");
-        fwd.input(By.cssSelector("input[type='password']")).sendKeys("root");
-
-        this.takeShot(driver, "login");
-
+    public void shouldLogin() {
+        goTo(loginPage).loginWithRoot();
 
-        fwd.button(By.tagName("button")).click();
+        loginPage.takeScreenshot("login.png");
     }
 }
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/ManageIT.java
similarity index 52%
copy from dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
copy to dubbo-admin-test/src/test/java/org/apache/dubbo/admin/ManageIT.java
index cfe11c3..89ce81f 100644
--- a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/ManageIT.java
@@ -18,30 +18,25 @@
  */
 package org.apache.dubbo.admin;
 
-import org.apache.commons.io.FileUtils;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
-import org.openqa.selenium.WebDriver;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.dubbo.admin.pages.ManagePage;
+import org.fluentlenium.core.annotation.Page;
+import org.junit.Test;
 
-import java.io.File;
-import java.io.IOException;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 
-public class BaseIT {
-    private Logger logger = LoggerFactory.getLogger(this.getClass());
+public class ManageIT extends BaseIT {
+    @Page
+    private ManagePage managePage;
 
-    public void takeShot(WebDriver webDriver, String name) {
-        TakesScreenshot scrShot = ((TakesScreenshot) webDriver);
+    @Test
+    public void shouldViewConfig() {
+        autoLogin();
 
-        File SrcFile = scrShot.getScreenshotAs(OutputType.FILE);
+        goTo(managePage).showConfigDetailFor("global");
 
-        File DestFile = new File("target/screens/" + name + ".png");
+        assertThat(managePage.getConfigDetail(), containsString("dubbo.registry.address"));
 
-        try {
-            FileUtils.copyFile(SrcFile, DestFile);
-        } catch (IOException e) {
-            logger.info("#takeShot# take shot fail", e);
-        }
+        managePage.takeScreenshot("manage-config.png");
     }
 }
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/MetricIT.java
similarity index 52%
copy from dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
copy to dubbo-admin-test/src/test/java/org/apache/dubbo/admin/MetricIT.java
index cfe11c3..b5d6bc1 100644
--- a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/MetricIT.java
@@ -18,30 +18,20 @@
  */
 package org.apache.dubbo.admin;
 
-import org.apache.commons.io.FileUtils;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
-import org.openqa.selenium.WebDriver;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.dubbo.admin.pages.MetricRelationPage;
+import org.fluentlenium.core.annotation.Page;
+import org.junit.Test;
 
-import java.io.File;
-import java.io.IOException;
+public class MetricIT extends BaseIT {
+    @Page
+    private MetricRelationPage relationPage;
 
-public class BaseIT {
-    private Logger logger = LoggerFactory.getLogger(this.getClass());
+    @Test
+    public void shouldShowRelation() {
+        autoLogin();
 
-    public void takeShot(WebDriver webDriver, String name) {
-        TakesScreenshot scrShot = ((TakesScreenshot) webDriver);
+        goTo(relationPage).showRelation();
 
-        File SrcFile = scrShot.getScreenshotAs(OutputType.FILE);
-
-        File DestFile = new File("target/screens/" + name + ".png");
-
-        try {
-            FileUtils.copyFile(SrcFile, DestFile);
-        } catch (IOException e) {
-            logger.info("#takeShot# take shot fail", e);
-        }
+        relationPage.takeScreenshot("metric-relation.png");
     }
 }
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/ServiceIT.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/ServiceIT.java
new file mode 100644
index 0000000..43d59d3
--- /dev/null
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/ServiceIT.java
@@ -0,0 +1,57 @@
+/*
+ *
+ *   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.dubbo.admin;
+
+import org.apache.dubbo.admin.pages.ServicePage;
+import org.fluentlenium.core.annotation.Page;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class ServiceIT extends BaseIT {
+    @Page
+    private ServicePage servicePage;
+
+    @Test
+    public void shouldCheckServiceInfo() {
+        autoLogin();
+
+        goTo(servicePage);
+
+        servicePage.takeScreenshot("service-page.png");
+
+        servicePage.checkDetailForService("org.apache.dubbo.admin.api.GreetingService:1.0.0");
+
+        servicePage.takeScreenshot("service-detail.png");
+    }
+
+    @Test
+    public void shouldTestService() {
+        autoLogin();
+
+        goTo(servicePage).checkTestDetailForService("org.apache.dubbo.admin.api.GreetingService:1.0.0")
+                .takeScreenshot("service-test-list.png");
+
+        servicePage.openTestDialogForMethod("sayHello").executeTestMethodWithParam("world")
+                .takeScreenshot("service-test-detail.png");
+
+        assertThat(servicePage.getTestMethodResult(), containsString("hello, world"));
+    }
+}
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/BasePage.java
similarity index 50%
copy from dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
copy to dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/BasePage.java
index cfe11c3..32a716f 100644
--- a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/BasePage.java
@@ -16,32 +16,10 @@
  *   limitations under the License.
  *
  */
-package org.apache.dubbo.admin;
+package org.apache.dubbo.admin.pages;
 
-import org.apache.commons.io.FileUtils;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
-import org.openqa.selenium.WebDriver;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.fluentlenium.core.FluentPage;
 
-import java.io.File;
-import java.io.IOException;
+public class BasePage extends FluentPage {
 
-public class BaseIT {
-    private Logger logger = LoggerFactory.getLogger(this.getClass());
-
-    public void takeShot(WebDriver webDriver, String name) {
-        TakesScreenshot scrShot = ((TakesScreenshot) webDriver);
-
-        File SrcFile = scrShot.getScreenshotAs(OutputType.FILE);
-
-        File DestFile = new File("target/screens/" + name + ".png");
-
-        try {
-            FileUtils.copyFile(SrcFile, DestFile);
-        } catch (IOException e) {
-            logger.info("#takeShot# take shot fail", e);
-        }
-    }
 }
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/LoginPage.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/LoginPage.java
new file mode 100644
index 0000000..7e2eb53
--- /dev/null
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/LoginPage.java
@@ -0,0 +1,57 @@
+/*
+ *
+ *   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.dubbo.admin.pages;
+
+import org.fluentlenium.core.annotation.PageUrl;
+import org.fluentlenium.core.domain.FluentWebElement;
+import org.openqa.selenium.support.FindBy;
+
+@PageUrl("/")
+public class LoginPage extends BasePage {
+
+    @FindBy(css = "input[name='username']")
+    private FluentWebElement usernameInput;
+
+    @FindBy(css = "input[type='password']")
+    private FluentWebElement passwordInput;
+
+    @FindBy(css = "button.primary")
+    private FluentWebElement loginButton;
+
+    @FindBy(css = "div.v-avatar")
+    private FluentWebElement avatarButton;
+
+    public LoginPage loginWithRoot() {
+        await().until(usernameInput).displayed();
+
+        usernameInput.fill().with("root");
+        passwordInput.fill().with("root");
+
+        loginButton.scrollToCenter();
+        loginButton.click();
+
+        return this;
+    }
+
+    public LoginPage logout() {
+        await().until(avatarButton).clickable();
+
+        return this;
+    }
+}
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/ManagePage.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/ManagePage.java
new file mode 100644
index 0000000..131fd1a
--- /dev/null
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/ManagePage.java
@@ -0,0 +1,57 @@
+/*
+ *
+ *   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.dubbo.admin.pages;
+
+import org.fluentlenium.core.annotation.PageUrl;
+import org.fluentlenium.core.domain.FluentWebElement;
+import org.openqa.selenium.By;
+import org.openqa.selenium.support.FindBy;
+
+@PageUrl("/#/management")
+public class ManagePage extends BasePage {
+    @FindBy(css = "table.v-datatable tbody tr")
+    private FluentWebElement configList;
+
+    @FindBy(css = "div.ace_content")
+    private FluentWebElement configCard;
+
+    public String getConfigDetail() {
+        return configCard.text();
+    }
+
+    public ManagePage showConfigDetailFor(String name) {
+        await().untilPredicate(p -> configList.asList().size() > 0);
+
+        for (FluentWebElement row : configList.asList()) {
+            for (FluentWebElement td : row.find(By.cssSelector("td"))) {
+                if (td.text().equalsIgnoreCase(name)) {
+                    for (FluentWebElement i : row.find(By.tagName("i"))) {
+                        if (i.text().equalsIgnoreCase("visibility")) {
+                            i.click();
+                            await().until(configCard).displayed();
+                            return this;
+                        }
+                    }
+
+                }
+            }
+        }
+        throw new RuntimeException("can't load detail");
+    }
+}
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/MetricRelationPage.java
similarity index 50%
copy from dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
copy to dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/MetricRelationPage.java
index cfe11c3..c2c7a30 100644
--- a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/BaseIT.java
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/MetricRelationPage.java
@@ -16,32 +16,14 @@
  *   limitations under the License.
  *
  */
-package org.apache.dubbo.admin;
+package org.apache.dubbo.admin.pages;
 
-import org.apache.commons.io.FileUtils;
-import org.openqa.selenium.OutputType;
-import org.openqa.selenium.TakesScreenshot;
-import org.openqa.selenium.WebDriver;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.fluentlenium.core.annotation.PageUrl;
 
-import java.io.File;
-import java.io.IOException;
-
-public class BaseIT {
-    private Logger logger = LoggerFactory.getLogger(this.getClass());
-
-    public void takeShot(WebDriver webDriver, String name) {
-        TakesScreenshot scrShot = ((TakesScreenshot) webDriver);
-
-        File SrcFile = scrShot.getScreenshotAs(OutputType.FILE);
-
-        File DestFile = new File("target/screens/" + name + ".png");
-
-        try {
-            FileUtils.copyFile(SrcFile, DestFile);
-        } catch (IOException e) {
-            logger.info("#takeShot# take shot fail", e);
-        }
+@PageUrl("/#/metrics/relation")
+public class MetricRelationPage extends BasePage {
+    public MetricRelationPage showRelation() {
+        isAt();
+        return this;
     }
 }
diff --git a/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/ServicePage.java b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/ServicePage.java
new file mode 100644
index 0000000..b9e8293
--- /dev/null
+++ b/dubbo-admin-test/src/test/java/org/apache/dubbo/admin/pages/ServicePage.java
@@ -0,0 +1,152 @@
+/*
+ *
+ *   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.dubbo.admin.pages;
+
+import com.google.common.base.Preconditions;
+import org.apache.commons.collections.CollectionUtils;
+import org.codehaus.plexus.util.StringUtils;
+import org.fluentlenium.core.annotation.PageUrl;
+import org.fluentlenium.core.domain.FluentList;
+import org.fluentlenium.core.domain.FluentWebElement;
+import org.openqa.selenium.By;
+import org.openqa.selenium.support.FindBy;
+
+import java.util.concurrent.TimeUnit;
+
+@PageUrl("/#/service")
+public class ServicePage extends BasePage {
+    @FindBy(css = "input#serviceSearch")
+    private FluentWebElement serviceSearchInput;
+
+    @FindBy(css = "button.primary")
+    private FluentWebElement serviceSearchButton;
+
+    @FindBy(css = "table.v-datatable tbody tr")
+    private FluentWebElement serviceList;
+
+    @FindBy(css = "div.v-content__wrap")
+    private FluentWebElement basicContainer;
+
+    @FindBy(css = "table.v-datatable tbody tr")
+    private FluentWebElement testMethodList;
+
+    @FindBy(css = "button#execute")
+    private FluentWebElement testExecButton;
+
+    @FindBy(css = "div[contenteditable='true']")
+    private FluentWebElement testExecInputs;
+
+    @FindBy(css = "div.it-test-method-result-container")
+    private FluentWebElement testResultContainer;
+
+    public ServicePage checkDetailForService(String fullName) {
+        await().until(serviceSearchInput).displayed();
+
+        serviceSearchInput.fill().with(fullName);
+
+        serviceSearchButton.click();
+
+        await().untilPredicate(p -> serviceList.asList().size() > 0);
+
+        for (FluentWebElement row : serviceList.asList()) {
+            for (FluentWebElement td : row.find(By.cssSelector("td"))) {
+                if (StringUtils.contains(fullName, td.text())) {
+                    row.find(By.cssSelector("a.success")).first().click();
+                    break;
+                }
+            }
+        }
+
+        await().untilPredicate(p -> basicContainer.text().contains("dubbo-admin-integration-provider"));
+
+        return this;
+    }
+
+    public ServicePage checkTestDetailForService(String fullName) {
+        await().until(serviceSearchInput).displayed();
+
+        serviceSearchInput.fill().with(fullName);
+
+        serviceSearchButton.click();
+
+        await().untilPredicate(p -> serviceList.asList().size() > 0);
+
+        for (FluentWebElement row : serviceList.asList()) {
+            for (FluentWebElement td : row.find(By.cssSelector("td"))) {
+                if (StringUtils.contains(fullName, td.text())) {
+                    row.find(By.cssSelector("a.v-btn--depressed")).first().click();
+                    break;
+                }
+            }
+        }
+
+        await().untilPredicate(p -> testMethodList.asList().size() > 0);
+
+        return this;
+    }
+
+    public ServicePage openTestDialogForMethod(String methodName) {
+        for (FluentWebElement method : testMethodList.asList()) {
+            FluentList<FluentWebElement> tds = method.find(By.tagName("td"));
+            if (CollectionUtils.isNotEmpty(tds) && StringUtils.equalsIgnoreCase(tds.get(0).text(), methodName)) {
+                method.find(By.cssSelector("span i")).click();
+                break;
+            }
+        }
+
+        await().until(testExecButton).clickable();
+
+        return this;
+    }
+
+    public ServicePage executeTestMethodWithParam(String... params) {
+        await().until(testExecInputs).displayed();
+
+        Preconditions.checkArgument(params.length == testExecInputs.asList().size(), "params not match input list");
+
+        for (int i = 0; i < testExecInputs.asList().size(); i++) {
+            testExecInputs.asList().get(i).fill().withText(params[i]);
+        }
+
+        testResultContainer.click();
+
+        try {
+            //sleep for a few seconds to make input works
+            TimeUnit.SECONDS.sleep(3);
+        } catch (InterruptedException e) {
+            //ignored
+        }
+
+        testExecButton.click();
+
+        await().atMost(10,TimeUnit.SECONDS).untilPredicate(p-> !getTestMethodResult().contains("{0}"));
+
+        return this;
+    }
+
+    public String getTestMethodResult() {
+        for (FluentWebElement resultRow : testResultContainer.find(By.cssSelector("table.jsoneditor-values tr"))) {
+            String resultAsString = resultRow.text().replace("\n", "");
+            if (StringUtils.contains(resultAsString, "Result")) {
+                return StringUtils.replaceOnce(resultAsString, "Result:", "");
+            }
+        }
+        throw new RuntimeException("can't get test method result");
+    }
+}
diff --git a/dubbo-admin-ui/src/components/test/TestMethod.vue b/dubbo-admin-ui/src/components/test/TestMethod.vue
index 8f6efa7..7372e1f 100644
--- a/dubbo-admin-ui/src/components/test/TestMethod.vue
+++ b/dubbo-admin-ui/src/components/test/TestMethod.vue
@@ -40,7 +40,7 @@
             <span class="red--text" v-if="success===false">{{ $t('fail')}}</span>
           </v-card-title>
           <v-card-text>
-            <json-editor v-model="result" name="Result" readonly></json-editor>
+            <json-editor v-model="result" name="Result" class="it-test-method-result-container" readonly></json-editor>
           </v-card-text>
         </v-card>
       </v-flex>