diff --git a/docs/README.md b/docs/README.md
index 9419513ba586bdb3ee6c8bd876350908f040fdb6..4e192ca655b4e168eaff70e0f8c80c4ce4782dc7 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -76,6 +76,11 @@ If you have been familiar with SkyWalking, you could use this catalog to find th
* [Dynamic Configuration](en/setup/backend/dynamic-config.md). Make configuration of OAP changed dynamic, from remote service or 3rd party configuration management system.
* [Uninstrumented Gateways](en/setup/backend/uninstrumented-gateways.md). Configure gateways/proxies that are not supported by SkyWalking agent plugins, to reflect the delegation in topology graph.
* [UI setup document](en/setup/backend/ui-setup.md).
+* [Contributing Guides](en/guides/README.md). Guides are for PMC member, committer or new contributor. At here, you can know how to contribute from beginning.
+ * [Contact us](en/guides/README.md#contact-us). Guide users about how to contact the official committer team or communicate with the project community.
+ * [Process to become official Apache SkyWalking Committer](en/guides/asf/committer.md). How to become an official committer or PMC member.
+ * [Compiling Guide](en/guides/How-to-build.md). Follow this to compile the whole project from the source code.
+ * [Agent plugin development guide](en/guides/Java-Plugin-Development-Guide.md). Guide the developer to write their plugin, and follow [plugin test requirement](en/guides/Plugin-test.md) when you push the plugin to the upstream.
* [Protocols](en/protocols/README.md). Protocols show the communication ways between agents/probes and backend. Anyone, interested in uplink telemetry data, definitely should read this.
* [FAQs](en/FAQ/README.md). Include a manifest, including already known setup problems, secondary developments experiments. When you are facing a problem, check here first.
diff --git a/docs/en/guides/Java-Plugin-Development-Guide.md b/docs/en/guides/Java-Plugin-Development-Guide.md
index 03c54c3438793a61633948ebdc532798be0bb6a2..3094627bd495b3eee409576f2541833f6cdf862f 100644
--- a/docs/en/guides/Java-Plugin-Development-Guide.md
+++ b/docs/en/guides/Java-Plugin-Development-Guide.md
@@ -365,7 +365,7 @@ Please follow there steps:
1. Create sub modules under `apm-sniffer/apm-sdk-plugin` or `apm-sniffer/optional-plugins`, and the name should include supported library name and versions
1. Follow this guide to develop. Make sure comments and test cases are provided.
1. Develop and test.
-1. Provide the automatic test cases. Read existing cases in `test/plugin/scenarios`, and add your new one.
+1. Provide the automatic test cases. Learn `how to write the plugin test case` from this [doc](Plugin-test.md)
1. Send the pull request and ask for review.
1. The plugin committers approve your plugins, plugin CI-with-IT, e2e and plugin tests passed.
1. The plugin accepted by SkyWalking.
diff --git a/docs/en/guides/Plugin-test.md b/docs/en/guides/Plugin-test.md
new file mode 100644
index 0000000000000000000000000000000000000000..0093516f5b9e49cae87761f758049cd6ce912ac5
--- /dev/null
+++ b/docs/en/guides/Plugin-test.md
@@ -0,0 +1,596 @@
+# Plugin automatic test framework
+
+Plugin test frameworks is designed for verifying the plugin function and compatible status. As there are dozens of plugins and
+hundreds versions need to be verified, it is impossible to do manually.
+The test framework uses container based tech stack, requires a set of real services with agent installed, then the test mock
+OAP backend is running to check the segment and register data sent from agents.
+
+Every plugin maintained in the main repo is required having its test cases, also matching the versions in the supported list doc.
+
+## Environment Requirements
+
+1. MacOS/Linux
+2. jdk 8+
+3. Docker
+4. Docker Compose
+
+## Case Base Image Introduction
+
+The test framework provides `JVM-container` and `Tomcat-container` base images. You could choose the suitable one for your test case, if either is suitable, **recommend choose `JVM-container`**.
+
+### JVM-container Image Introduction
+
+[JVM-container](../../../test/plugin/containers/jvm-container) uses `openjdk:8` as the basic image.
+The test case project is required to be packaged as `project-name.zip`, including `startup.sh` and uber jar, by using `mvn package`.
+
+Take the following test projects as good examples
+* [sofarpc-scenario](../../../test/plugin/scenarios/sofarpc-scenario) as a single project case.
+* [webflux-scenario](../../../test/plugin/scenarios/webflux-scenario) as a case including multiple projects.
+
+### Tomcat-container Image Introduction
+
+[Tomcat-container](../../../test/plugin/containers/tomcat-container) uses `tomcat:8.5.42-jdk8-openjdk` as the basic image.
+The test case project is required to ba packaged as `project-name.war` by using `mvn package`.
+
+Take the following test project as a good example
+* [spring-4.3.x-scenario](https://github.com/apache/skywalking/tree/master/test/plugin/scenarios/spring-4.3.x-scenario)
+
+
+## Test project hierarchical structure
+The test case is an independent maven project, and it is required to be packaged as a war tar ball or zip file, depends
+on the chosen basic image. Also, two external accessible endpoints, mostly two URLs, are required.
+
+All test case codes should be in `org.apache.skywalking.apm.testcase.*` package, unless there are some codes expected being instrumented,
+then the classes could be in `test.org.apache.skywalking.apm.testcase.*` package.
+
+**JVM-container test project hierarchical structure**
+
+```
+[plugin-scenario]
+ |- [bin]
+ |- startup.sh
+ |- [config]
+ |- expectedData.yaml
+ |- [src]
+ |- [main]
+ |- ...
+ |- [resource]
+ |- log4j2.xml
+ |- pom.xml
+ |- configuration.yaml
+ |- support-version.list
+
+[] = directory
+```
+
+**Tomcat-container test project hierarchical structure**
+
+```
+[plugin-scenario]
+ |- [config]
+ |- expectedData.yaml
+ |- [src]
+ |- [main]
+ |- ...
+ |- [resource]
+ |- log4j2.xml
+ |- [webapp]
+ |- [WEB-INF]
+ |- web.xml
+ |- pom.xml
+ |- configuration.yaml
+ |- support-version.list
+
+[] = directory
+```
+
+## Test case configuration files
+The following files are required in every test case.
+
+File Name | Descriptions
+---|---
+`configuration.yml` | Declare the basic case inform, including, case name, entrance endpoints, mode, dependencies.
+`expectedData.yaml` | Describe the expected Segment(s), including two major parts, (1) Register metadata (2) Segments
+`support-version.list` | List the target versions for this case
+`startup.sh` |`JVM-container` only, don't need this when use`Tomcat-container`
+
+`*` support-version.list format requires every line for a singe version. Could use `#` to comment out this version.
+
+### configuration.yml
+
+| Field | description
+| --- | ---
+| type | Image type, options, `jvm` or `tomcat`. Required.
+| entryService | The entrance endpoint(URL) for test case access. Required.
+| healthCheck | The health check endpoint(URL) for test case access. Required.
+| startScript | Path of start up script. Required in `type: jvm` only.
+| framework | Case name.
+| runningMode | Running mode whether with the optional plugin, options, `default`(default), `with_optional`, `with_bootstrap`
+| withPlugins | Plugin selector rule. eg:`apm-spring-annotation-plugin-*.jar`. Required when `runningMode=with_optional` or `runningMode=with_bootstrap`.
+| environment | Same as `docker-compose#environment`.
+| depends_on | Same as `docker-compose#depends_on`.
+| dependencies | Same as `docker-compose#services`, `image、links、hostname、environment、depends_on` are supported.
+
+**Notice:, `docker-compose` active only when `dependencies` is only blank.**
+
+**runningMode** option description.
+
+| Option | description
+| --- | ---
+| default | Active all plugins in `plugin` folder like the official distribution agent.
+| with_optional | Active `default` and plugins in `optional-plugin` by the give selector.
+| with_bootstrap | Active `default` and plugins in `bootstrap-plugin` by the give selector.
+
+with_optional/with_bootstrap supports multiple selectors, separated by `;`.
+
+**File Format**
+
+```
+type:
+entryService:
+healthCheck:
+startScript:
+framework:
+runningMode:
+withPlugins:
+environment:
+ ...
+depends_on:
+ ...
+dependencies:
+ service1:
+ image:
+ hostname:
+ expose:
+ ...
+ environment:
+ ...
+ depends_on:
+ ...
+ links:
+ ...
+ entrypoint:
+ ...
+ healthcheck:
+ ...
+```
+
+* dependencies supports docker compose `healthcheck`. But the format is a little difference. We need `-` as the start of every config item,
+and describe it as a string line.
+
+Such as in official doc, the health check is
+```yaml
+healthcheck:
+ test: ["CMD", "curl", "-f", "http://localhost"]
+ interval: 1m30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+```
+
+The here, you should write as
+```yaml
+healthcheck:
+ - 'test: ["CMD", "curl", "-f", "http://localhost"]'
+ - "interval: 1m30s"
+ - "timeout: 10s"
+ - "retries: 3"
+ - "start_period: 40s"
+```
+
+In some cases, the dependency service, mostly 3rd party server like SolrJ server, is required to keep the same version
+as client lib version, which defined as `${test.framework.version}` in pom. Could use `${CASE_SERVER_IMAGE_VERSION}`
+as the version number, it will be changed in the test for every version.
+
+> Don't support resource related configurations, such as volumes, ports and ulimits. Because in test scenarios,
+> don't need mapping any port to the host VM, or mount any folder.
+
+**Take following test cases as examples**
+* [dubbo-2.7.x with JVM-container](../../../test/plugin/scenarios/dubbo-2.7.x-scenario/configuration.yml)
+* [jetty with Tomcat-container](../../../test/plugin/scenarios/jetty-scenario/configuration.yml)
+* [gateway with runningMode](../../../test/plugin/scenarios/gateway-scenario/configuration.yml)
+* [canal with docker-compose](../../../test/plugin/scenarios/canal-scenario/configuration.yml)
+
+### expectedData.yaml
+
+**Operator for number**
+
+| Operator | Description |
+| :--- | :--- |
+| `nq` | Not equal |
+| `eq` | Equal(default) |
+| `ge` | Greater than or equal |
+| `gt` | Greater than |
+
+**Operator for String**
+
+| Operator | Description |
+| :--- | :--- |
+| `not null` | Not null |
+| `null` | Null or empty String |
+| `eq` | Equal(default) |
+
+
+**Register verify description format**
+```yml
+registryItems:
+ applications:
+ - APPLICATION_CODE: APPLICATION_ID(int)
+ ...
+ instances:
+ - APPLICATION_CODE: INSTANCE_COUNT(int)
+ ...
+ operationNames:
+ - APPLICATION_CODE: [ SPAN_OPERATION(string), ... ]
+ ...
+```
+
+
+| Field | Description
+| --- | ---
+| applications | The registered service codes. Normally, not 0 should be enough.
+| instances | The number of service instances exists in this test case.
+| operationNames | All endpoint registered in this test case. Also means, the operation names of all entry and exit spans.
+
+
+**Segment verify description format**
+```yml
+segments:
+-
+ applicationCode: APPLICATION_CODE(string)
+ segmentSize: SEGMENT_SIZE(int)
+ segments:
+ - segmentId: SEGMENT_ID(string)
+ spans:
+ ...
+```
+
+
+| Field | Description
+| --- | ---
+| applicationCode | Service code.
+| segmentSize | The number of segments is expected.
+| segmentId | trace ID.
+| spans | segment span list. Follow the next section to see how to describe every span.
+
+**Span verify description format**
+
+**Notice**: The order of span list should follow the order of the span finish time.
+
+```yml
+ operationName: OPERATION_NAME(string)
+ operationId: SPAN_ID(int)
+ parentSpanId: PARENT_SPAN_ID(int)
+ spanId: SPAN_ID(int)
+ startTime: START_TIME(int)
+ endTime: END_TIME(int)
+ isError: IS_ERROR(string: true, false)
+ spanLayer: SPAN_LAYER(string: DB, RPC_FRAMEWORK, HTTP, MQ, CACHE)
+ spanType: SPAN_TYPE(string: Exit, Entry, Local )
+ componentName: COMPONENT_NAME(string)
+ componentId: COMPONENT_ID(int)
+ tags:
+ - {key: TAG_KEY(string), value: TAG_VALUE(string)}
+ ...
+ logs:
+ - {key: LOG_KEY(string), value: LOG_VALUE(string)}
+ ...
+ peer: PEER(string)
+ peerId: PEER_ID(int)
+ refs:
+ - {
+ parentSpanId: PARENT_SPAN_ID(int),
+ parentTraceSegmentId: PARENT_TRACE_SEGMENT_ID(string),
+ entryServiceName: ENTRY_SERVICE_NAME(string),
+ networkAddress: NETWORK_ADDRESS(string),
+ parentServiceName: PARENT_SERVICE_NAME(string),
+ entryApplicationInstanceId: ENTRY_APPLICATION_INSTANCE_ID(int)
+ }
+ ...
+```
+
+| Field | Description
+|--- |---
+| operationName | Span Operation Name
+| operationId | Should be 0 for now
+| parentSpanId | Parent span id. **Notice**: The parent span id of the first span should be -1.
+| spanId | Span Id. **Notice**, start from 0.
+| startTime | Span start time. It is impossible to get the accurate time, not 0 should be enough.
+| endTime | Span finish time. It is impossible to get the accurate time, not 0 should be enough.
+| isError | Span status, true or false.
+| componentName | Component name, should be null in most cases, use component id instead.
+| componentId | Component id for your plugin.
+| tags | Span tag list. **Notice**, Keep in the same order as the plugin coded.
+| logs | Span log list. **Notice**, Keep in the same order as the plugin coded.
+| SpanLayer | Options, DB, RPC_FRAMEWORK, HTTP, MQ, CACHE
+| SpanType | Span type, options, Exit, Entry or Local
+| peer | Remote network address, IP + port mostly. For exit span, this should be required.
+| peerId | Not 0 for now.
+
+
+The verify description for SegmentRef,
+
+| Field | Description
+|---- |----
+| parentSpanId | Parent SpanID, pointing to the span id in the parent segment.
+| parentTraceSegmentId | Parent SegmentID. Format is `${APPLICATION_CODE[SEGMENT_INDEX]}`, pointing to the index of parent service segment list.
+| entryServiceName | Entrance service code of the whole distributed call chain. Such HttpClient entryServiceName is `/httpclient-case/case/httpclient`
+| networkAddress | The peer value of parent exit span.
+| parentServiceName | Parent service code.
+| entryApplicationInstanceId | Not 0 should be enough.
+
+
+### startup.sh
+
+This script provide a start point to JVM based service, most of them starts by a `java -jar`, with some variables.
+The following system environment variables are available in the shell.
+
+| Variable | Description |
+|:---- |:---- |
+| agent_opts | Agent plugin opts, check the detail in plugin doc or the same opt added in this PR. |
+| SCENARIO_NAME | Service name. Default same as the case folder name |
+| SCENARIO_VERSION | Version |
+| SCENARIO_ENTRY_SERVICE | Entrance URL to access this service |
+| SCENARIO_HEALTH_CHECK_URL | Health check URL |
+
+
+> `${agent_opts}` is required to add into your `java -jar` command, which including the parameter injected by test framework, and
+> make agent installed. All other parameters should be added after `${agent_opts}`.
+
+The test framework will set the service name as the test case folder name by default, but in some cases, there are more
+than one test projects are required to run in different service codes, could set it explicitly like the following example.
+
+Example
+```bash
+home="$(cd "$(dirname $0)"; pwd)"
+
+java -jar ${agent_opts} "-Dskywalking.agent.service_name=jettyserver-scenario" ${home}/../libs/jettyserver-scenario.jar &
+sleep 1
+
+java -jar ${agent_opts} "-Dskywalking.agent.service_name=jettyclient-scenario" ${home}/../libs/jettyclient-scenario.jar &
+
+```
+
+> Only set this or use other skywalking options when it is really necessary.
+
+**Take the following test cases as examples**
+* [undertow](../../../test/plugin/scenarios/undertow-scenario/bin/startup.sh)
+* [webflux](../../../test/plugin/scenarios/webflux-scenario/webflux-dist/bin/startup.sh)
+
+
+## Best Practices
+### Recommendations for pom
+
+```xml
+
+
+
+
+ 4.3
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ ${test.framework.version}
+
+ ...
+
+
+
+
+ httpclient-4.3.x-scenario
+ ....
+
+```
+
+### How To Implement Heartbeat Service
+
+Heartbeat service is designed for checking the service available status. This service is a simple HTTP service, returning 200 means the
+target service is ready. Then the traffic generator will access the entry service and verify the expected data.
+User should consider to use this service to detect such as whether the dependent services are ready, especially when
+dependent services are database or cluster.
+
+Notice, because heartbeat service could be traced fully or partially, so, segmentSize in `expectedData.yaml` should use `ge` as the operator,
+and don't include the segments of heartbeat service in the expected segment data.
+
+### The example Process of Writing Expected Data
+
+Expected data file, `expectedData.yaml`, includes `RegistryItems` and `SegmentIntems`.
+
+We are using the HttpClient plugin to show how to write the expected data.
+
+There are two key points of testing
+1. Whether is HttpClient span created.
+1. Whether the ContextCarrier created correctly, and propagates across processes.
+
+```
++-------------+ +------------------+ +-------------------------+
+| Browser | | Case Servlet | | ContextPropagateServlet |
+| | | | | |
++-----|-------+ +---------|--------+ +------------|------------+
+ | | |
+ | | |
+ | WebHttp +-+ |
+ +------------------------> |-| HttpClient +-+
+ | |--------------------------------> |-|
+ | |-| |-|
+ | |-| |-|
+ | |-| <--------------------------------|
+ | |-| +-+
+ | <--------------------------| |
+ | +-+ |
+ | | |
+ | | |
+ | | |
+ | | |
+ + + +
+```
+
+#### RegistryItems
+
+HttpClient test case is running in Tomcat container, only one instance exists, so
+1. instance number is 1
+1. applicationId is not 0
+1. Because we have two servlet mapping paths, so two operation names. No health check operation name here.
+
+```yml
+registryItems:
+ applications:
+ - {httpclient-case: nq 0}
+ instances:
+ - {httpclient-case: 1}
+ operationNames:
+ - httpclient-case: [/httpclient-case/case/httpclient,/httpclient-case/case/context-propagate]
+```
+
+#### segmentItems
+
+By following the flow of HttpClient case, there should be two segments created.
+1. Segment represents the CaseServlet access. Let's name it as `SegmentA`.
+1. Segment represents the ContextPropagateServlet access. Let's name it as `SegmentB`.
+
+```yml
+segments:
+ - applicationCode: httpclient-case
+ segmentSize: ge 2 # Could have more than one health check segments, because, the dependency is not standby.
+```
+
+Because Tomcat plugin is a default plugin of SkyWalking, so, in SegmentA, there are two spans
+1. Tomcat entry span
+1. HttpClient exit span
+
+SegmentA span list should like following
+```yml
+ - segmentId: not null
+ spans:
+ - operationName: /httpclient-case/case/context-propagate
+ operationId: eq 0
+ parentSpanId: 0
+ spanId: 1
+ startTime: nq 0
+ endTime: nq 0
+ isError: false
+ spanLayer: Http
+ spanType: Exit
+ componentName: null
+ componentId: eq 2
+ tags:
+ - {key: url, value: 'http://127.0.0.1:8080/httpclient-case/case/context-propagate'}
+ - {key: http.method, value: GET}
+ logs: []
+ peer: null
+ peerId: eq 0
+ - operationName: /httpclient-case/case/httpclient
+ operationId: eq 0
+ parentSpanId: -1
+ spanId: 0
+ startTime: nq 0
+ endTime: nq 0
+ spanLayer: Http
+ isError: false
+ spanType: Entry
+ componentName: null
+ componentId: 1
+ tags:
+ - {key: url, value: 'http://localhost:{SERVER_OUTPUT_PORT}/httpclient-case/case/httpclient'}
+ - {key: http.method, value: GET}
+ logs: []
+ peer: null
+ peerId: eq 0
+```
+
+SegmentB should only have one Tomcat entry span, but includes the Ref pointing to SegmentA.
+
+SegmentB span list should like following
+```yml
+- segmentId: not null
+ spans:
+ -
+ operationName: /httpclient-case/case/context-propagate
+ operationId: eq 0
+ parentSpanId: -1
+ spanId: 0
+ tags:
+ - {key: url, value: 'http://127.0.0.1:8080/httpclient-case/case/context-propagate'}
+ - {key: http.method, value: GET}
+ logs: []
+ startTime: nq 0
+ endTime: nq 0
+ spanLayer: Http
+ isError: false
+ spanType: Entry
+ componentName: null
+ componentId: 1
+ peer: null
+ peerId: eq 0
+ refs:
+ - {parentSpanId: 1, parentTraceSegmentId: "${httpclient-case[0]}", entryServiceName: "/httpclient-case/case/httpclient", networkAddress: "127.0.0.1:8080",parentServiceName: "/httpclient-case/case/httpclient",entryApplicationInstanceId: nq 0 }
+```
+
+## Local Test and Pull Request To The Upstream
+
+First of all, the test case project could be compiled successfully, with right project structure and be able to deploy.
+The developer should test the start script could run in Linux/MacOS, and entryService/health services are able to provide
+the response.
+
+You could run test by using following commands
+
+```bash
+cd ${SKYWALKING_HOME}
+bash ./test/pugin/run.sh -f ${scenario_name}
+```
+
+**Notice**,if codes in `./apm-sniffer` have been changed, no matter because your change or git update,
+please recompile the `skywalking-agent`. Because the test framework will use the existing `skywalking-agent` folder,
+rather than recompiling it every time.
+
+Use `${SKYWALKING_HOME}/test/plugin/run.sh -h` to know more command options.
+
+If the local test passed, then you could add it to Jenkins file, which will drive the tests running on the official SkyWalking INFRA.
+We have 3 JenkinsFile to control the test jobs, `jenkinsfile-agent-test`, `jenkinsfile-agent-test-2` and `jenkinsfile-agent-test-3`(maybe will have 4 later)
+each file declares two parallel groups. Please check the prev agent related PRs, and add your case to the fastest group,
+in order to make the whole test finished as soon as possible.
+
+Every test case is a Jenkins stage. Please use the scenario name, version range and version number to combine the name,
+take the existing one as a reference. And update the total case number in `Test Cases Report` stage.
+
+Example
+
+```
+stage('Test Cases Report (15)') { # 15=12+3 The total number of test cases
+ steps {
+ echo "Test Cases Report"
+ }
+}
+
+stage('Run Agent Plugin Tests') {
+ when {
+ expression {
+ return sh(returnStatus: true, script: 'bash tools/ci/agent-build-condition.sh')
+ }
+ }
+ parallel {
+ stage('Group1') {
+ stages {
+ stage('spring-cloud-gateway 2.1.x (3)') { # For Spring clound gateway 2.1.x, having 3 versions to be tested.
+ steps {
+ sh 'bash test/plugin/run.sh gateway-scenario'
+ }
+ }
+ }
+ ...
+ }
+ stage('Group2') {
+ stages {
+ stage('solrj 7.x (12)') { # For Solrj 7.x, having 12 versions to be tested.
+ steps {
+ sh 'bash test/plugin/run.sh solrj-7.x-scenario'
+ }
+ }
+ }
+ ...
+ }
+ }
+}
+```