Repository: espressif/pytest-embedded (language: python)

Issue:
Readthedocs Flyout not Working (RDT-347)
related: https://github.com/squidfunk/mkdocs-material/issues/3104


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 494031cb..cc4dde11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,8 +51,8 @@ report.xml
 *.mo
 *.pot
 
-# mkdocs documentation
-site/
+# sphinx documentation
+docs/_build
 
 # PyBuilder
 target/
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 4c1ddc4a..bf9e882d 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -4,11 +4,12 @@
 
 version: 2
 
-mkdocs:
-  configuration: mkdocs.yml
+build:
+  os: ubuntu-22.04
+  tools:
+    python: "3.7"
 
 python:
-  version: "3.7"
   install:
     - requirements: docs/requirements.txt
     - requirements: all_local_packages.txt
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 184c838e..7c21d559 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,20 @@
+# CHANGELOG
+
+## v1.3.0 (2023-05-31) (yanked)
+
+### Feat
+
+- **idf**: `IdfApp` add new property `is_xtensa`, `is_riscv32`
+- **idf**: add `confirm_write` method to make sure the write happens
+- **idf**: add `run_single_board_case` function
+- **idf**: expose pytest_embedded_idf.UnittestMenuCase
+- **qemu**: support flash encryption workflow
+- **qemu**: support riscv32 targets, remove target restriction
+
+### Fix
+
+- **qemu**: Add more idf non-target related functions when enabling service `idf`
+
 ## v1.2.5 (2023-04-11)
 
 ### Feat
diff --git a/README.md b/README.md
index b6b655a7..65224c3f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-# pytest-embedded [![Documentation Status](https://readthedocs.com/projects/espressif-pytest-embedded/badge/?version=latest)](https://docs.espressif.com/projects/pytest-embedded/en/latest/?badge=latest) [![Python 3.7+](https://img.shields.io/pypi/pyversions/pytest-embedded)]()
+# pytest-embedded
+
+[![Documentation Status](https://readthedocs.com/projects/espressif-pytest-embedded/badge/?version=latest)](https://docs.espressif.com/projects/pytest-embedded/en/latest/?badge=latest) ![Python 3.7+](https://img.shields.io/pypi/pyversions/pytest-embedded)
 
 A pytest plugin that has multiple services available for various functionalities. Designed for embedded testing.
 
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 786b75d5..66efc0fe 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,1 +1,2 @@
---8<-- "CHANGELOG.md"
+```{include} ../CHANGELOG.md
+```
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index ea38c9bf..78caf34e 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -1,1 +1,2 @@
---8<-- "CONTRIBUTING.md"
+```{include} ../CONTRIBUTING.md
+```
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 00000000..d4bb2cbb
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/_static/espressif-logo.svg b/docs/_static/espressif-logo.svg
new file mode 100644
index 00000000..1725310b
--- /dev/null
+++ b/docs/_static/espressif-logo.svg
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 593.3 134.2" style="enable-background:new 0 0 593.3 134.2;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FF3034;}
+	.st1{fill:#666565;}
+	.st2{enable-background:new    ;}
+	.st3{fill:#FFFEFE;}
+	.st4{fill:none;stroke:#E71D16;stroke-width:3;stroke-miterlimit:10;}
+</style>
+<g id="XMLID_230_">
+	<path id="XMLID_28_" class="st0" d="M61.7,90.5c0,3.6-2.9,6.5-6.5,6.5s-6.5-2.9-6.5-6.5s2.9-6.5,6.5-6.5S61.7,86.9,61.7,90.5"/>
+	<path id="XMLID_27_" class="st0" d="M118.9,83.7C114.7,54,91.1,30.4,61.4,26.2c-3.5,1.8-6.7,4.2-9.5,6.9v6.3
+		c29.7,0,53.8,24.1,53.8,53.8h6.3C114.7,90.4,117,87.2,118.9,83.7"/>
+	<path id="XMLID_26_" class="st0" d="M123.8,63.8c0-23.5-19.1-42.6-42.6-42.6c-1.5,0-2.9,0.1-4.4,0.2l-1,2.8
+		c20.9,7.3,37.5,23.9,44.9,44.8l2.8-1C123.7,66.7,123.8,65.3,123.8,63.8"/>
+	<path id="XMLID_25_" class="st0" d="M81.1,96.1c1-10.6-3.7-20.4-11.6-26.4c-4.2-3.2-9.2-5.2-14.8-5.8c-1.5-0.2-2.5-1.4-2.4-2.9
+		c0.1-1.3,1.2-2.3,2.6-2.4c0.1,0,0.2,0,0.3,0c6,0.6,11.6,2.7,16.2,6c10,6.9,16.1,19,14.8,32c-0.2,2.1-0.6,4-1.2,6l7.7,2.2
+		c2.3-0.7,4.5-1.5,6.7-2.5c0.5-2.9,0.9-6,0.9-9.1c0-24.4-18-44.6-41.4-48c-2.8-0.4-5.7-0.4-7.7,0.1c-7.2,1.8-12.5,8.3-12.5,16.1
+		c0,7.4,4.8,13.7,11.5,15.8c1.3,0.4,3.2,0.6,4.2,0.7l0,0c7.3,1.3,12.9,7.7,12.9,15.3c0,3.1-0.9,6-2.5,8.4l5.3,3.4
+		c2.6,0.7,5.2,1.1,7.9,1.3C79.7,103.2,80.8,99.8,81.1,96.1"/>
+	<path id="XMLID_24_" class="st0" d="M81.7,114.4c-13.5,0-26.2-5.3-35.8-14.8C36.3,90,31.1,77.3,31.1,63.8S36.4,37.6,45.9,28
+		c0.7-0.7,2-0.7,2.7,0s0.7,2,0,2.7c-8.8,8.8-13.7,20.6-13.7,33.1S39.8,88,48.6,96.9c8.8,8.8,20.6,13.7,33.1,13.7s24.2-4.9,33.1-13.7
+		c0.7-0.7,2-0.7,2.7,0s0.7,2,0,2.7C107.9,109.1,95.2,114.4,81.7,114.4"/>
+</g>
+<g>
+	<path id="XMLID_226_" class="st1" d="M250.7,102.8V32.1c5.2,0,12.6,0,22.1,0c3,0,6-0.1,8.3,0c3.9,0.1,6.7,0.8,9.8,3.8
+		c2.6,2.5,3.3,5.1,3.5,8.5c0,2.7,0,5.9,0,9.8c0,1.9,0,3.7,0,5.5c0,1.9,0.1,2.1-0.1,3.6c-0.4,3.9-2,6.1-3.7,8
+		c-1.5,1.7-3.3,2.8-6.1,3.3c-2.5,0.4-6,0.3-9.5,0.3c-3.7,0-11,0-14.6,0l-0.1,27.9C260.3,102.8,250.7,102.8,250.7,102.8z M260.2,66.5
+		h12.4c1.9,0,4.1,0.1,6.3-0.1c1.7-0.2,3.3-0.4,4.3-1.3c2.3-2.2,1.9-5.3,1.9-10c0-2,0-3.7-0.1-5.5c-0.1-1.5,0.3-5.4-1.5-7.3
+		c-1.9-2-4.2-1.7-5.8-1.7c-1.7,0-3.3,0-5,0h-12.4L260.2,66.5z M272.8,32.1c3,0,5.8-0.1,8.3,0c3.9,0.1,6.7,0.8,9.8,3.8"/>
+	<path id="XMLID_223_" class="st1" d="M349.7,102.9h-10.3c0,0-3-15.5-4.4-20.8c-1.3-4.8-3.2-6.8-4.5-7.5c-1.7-0.9-3.4-1.1-5.1-1.1
+		l-12.6-0.1l0.1,29.4h-9.1V32.4c5.3,0,12.5,0,22,0c2.1,0,4.2,0,6.2,0c1.8,0,3.8,0,5.2,0.3c2.9,0.7,4.1,1.6,5.6,3.1
+		c3.1,3.2,3.2,8.9,3.2,16.7c0,5.2,0.2,11.4-1.5,14.3c-1.4,2.5-3.4,3.4-7.1,4.2H337c4.4,1.2,6.7,4.1,7.8,10L349.7,102.9z M312.9,65.2
+		h12.5c2.4,0,6.3,0.2,8.5-0.3c0.9-0.2,1.4-0.6,1.8-1.1c0.4-0.5,0.5-1.1,0.7-1.8c0.3-2.1,0.2-5.5,0.2-8.5c0-2.2,0-4.7-0.1-7.1
+		c0-1.9-0.4-3.5-1.3-4.4c-1.2-1.2-2.6-1.2-4.8-1.3c-1.7-0.1-3.8,0-5.4,0l-12.3,0.1L312.9,65.2z"/>
+	<g>
+		<path class="st1" d="M188.6,94.2l-0.1,8.7c-5.7,0-14.6,0-27.3,0c-3.9,0-7.1-1.1-9.3-3.4c-2.3-2.3-3.5-5.4-3.5-9.2V44.8
+			c0.1-3.9,1.2-7,3.5-9.2c2.3-2.3,5.4-3.4,9.3-3.4c12.6,0,21.7,0.1,27.3,0.1V41h-25.7c-1.9,0-3.2,0.5-3.9,1.5c-0.8,1-1.2,2.7-1.2,5
+			v14.1h26.9v8.3h-26.9v17.9c0,2.4,0.4,4.1,1.2,5c0.8,1,2.1,1.5,3.9,1.5L188.6,94.2z"/>
+		<path class="st1" d="M396.5,94.1l-0.1,8.7c-5.7,0-14.6,0-27.3,0c-3.9,0-7.1-1.1-9.3-3.4c-2.3-2.3-3.5-5.4-3.5-9.2V44.6
+			c0.1-3.9,1.2-7,3.5-9.2c2.3-2.3,5.4-3.4,9.3-3.4c12.6,0,21.7,0.1,27.3,0.1v8.7h-25.7c-1.9,0-3.2,0.5-3.9,1.5c-0.8,1-1.2,2.7-1.2,5
+			v14.1h26.9v8.3h-26.9v17.9c0,2.4,0.4,4.1,1.2,5c0.8,1,2.1,1.5,3.9,1.5L396.5,94.1z"/>
+
+			<image style="display:none;overflow:visible;enable-background:new    ;" width="183" height="304" xlink:href="F2A76EEE96E44F61.png"  transform="matrix(0.2353 0 0 0.2353 253.0778 31.2379)">
+		</image>
+
+			<image style="display:none;overflow:visible;enable-background:new    ;" width="196" height="304" xlink:href="F2A76EEE96E44F63.png"  transform="matrix(0.2339 0 0 0.2339 307.7243 31.4485)">
+		</image>
+		<path class="st1" d="M519.1,102.7h-9.3V32.3h9.3V102.7z"/>
+		<path class="st1" d="M568,40.8h-24.1c-2.2,0-3.7,0.5-4.6,1.5s-1.3,2.7-1.3,5v15.2h26.1v8.3H538v31.8h-9.3V45.3
+			c0-4.1,1.2-7.4,3.5-9.7s5.5-3.4,9.4-3.4c12.3,0,20.8,0,26.4,0.1V40.8z"/>
+	</g>
+	<path id="XMLID_221_" class="st1" d="M225.4,71.5c-3.7-0.1-13.6,0.3-16.6-0.3c-4.6-0.8-7.3-2.8-9.2-6.1c-1.9-3.3-1.4-9.1-1.4-14.4
+		c0-5.7-0.1-9.7,1.5-12.8c0.6-1.2,1.2-2.1,2.3-3c3.5-3.1,9.5-2.6,17.4-2.6c7.5,0,12.9-0.1,18.5,0v8.5c-4.9,0-13.2-0.1-20.4-0.1
+		c-2.8,0-5.5-0.1-7.4,0.3c-0.4,0.1-0.6,0.2-0.9,0.4c-0.4,0.2-0.7,0.6-1,1c-0.7,1.3-0.7,3.2-0.7,5.3c0,2.3-0.2,10.5,0.5,11.6
+		c1.2,2,2.7,2,4.8,2c1.3,0,5.2,0,7.8,0c3.1,0,6.7,0,8.2,0.2c2.6,0.3,4.7,1,6.2,1.8c2.1,1.1,3.2,2.4,4.3,4.2c1.2,2.1,1.4,4.2,1.4,6.9
+		c0,2,0,4.7,0,6.9c0,2.5,0.1,7.1-0.3,9.6c-0.6,4.4-2.1,7.2-4.4,9.1c-1.7,1.4-3.8,2.3-6.7,2.6c-1.5,0.2-3.3,0.2-5.3,0.2
+		c-1.5,0-3.2,0-5,0c-6.6,0-14.2,0-20.9,0V94c12.2,0,20,0,20.8,0c1.6,0,6.1,0,7.1,0c2.1,0,3.1-0.1,4-1c0.9-0.8,1-2.2,1-4.2
+		c0-0.4,0-2.7,0-4.9c0-1.9,0-3.7,0-4.1c0-2.6,0.2-5.3-1.1-6.7C228.8,71.7,227.9,71.5,225.4,71.5z"/>
+	<path id="XMLID_214_" class="st1" d="M433.3,71.5c-3.7-0.1-13.6,0.3-16.6-0.3c-4.6-0.8-7.3-2.8-9.2-6.1c-1.9-3.3-1.4-9.1-1.4-14.4
+		c0-5.7-0.1-9.7,1.5-12.8c0.6-1.2,1.2-2.1,2.3-3c3.5-3.1,9.5-2.6,17.4-2.6c7.5,0,12.9-0.1,18.5,0v8.5c-4.9,0-13.2-0.1-20.4-0.1
+		c-2.8,0-5.5-0.1-7.4,0.3c-0.4,0.1-0.6,0.2-0.9,0.4c-0.4,0.2-0.7,0.6-1,1c-0.7,1.3-0.7,3.2-0.7,5.3c0,2.3-0.2,10.5,0.5,11.6
+		c1.2,2,2.7,2,4.8,2c1.3,0,5.2,0,7.8,0c3.1,0,6.7,0,8.2,0.2c2.6,0.3,4.7,1,6.2,1.8c2.1,1.1,3.1,2.2,4.2,4.2c1.2,2.1,1.5,4.2,1.5,6.9
+		c0,2,0,4.7,0,6.9c0,2.5,0.1,7.1-0.3,9.6c-0.6,4.4-2.1,7.2-4.4,9.1c-1.7,1.4-3.8,2.3-6.7,2.6c-1.5,0.2-3.3,0.2-5.3,0.2
+		c-1.5,0-3.2,0-5,0c-6.6,0-14.2,0-20.9,0V94c12.2,0,20,0,20.8,0c1.6,0,6.1,0,7.1,0c2.1,0,3.1-0.1,4-1c0.9-0.8,1-2.2,1-4.2
+		c0-0.4,0-2.7,0-4.9c0-1.9,0-3.7,0-4.1c0-2.6,0.2-5.3-1.1-6.7C436.7,71.7,435.8,71.5,433.3,71.5z"/>
+	<path id="XMLID_212_" class="st1" d="M484.6,71.5c-3.7-0.1-13.6,0.3-16.6-0.3c-4.6-0.8-7.3-2.8-9.2-6.1c-1.9-3.3-1.4-9.1-1.4-14.4
+		c0-5.7-0.1-9.7,1.5-12.8c0.6-1.2,1.2-2.1,2.3-3c3.5-3.1,9.5-2.6,17.4-2.6c7.5,0,12.9-0.1,18.5,0v8.5c-4.9,0-13.2-0.1-20.4-0.1
+		c-2.8,0-5.5-0.1-7.4,0.3c-0.4,0.1-0.6,0.2-0.9,0.4c-0.4,0.2-0.7,0.6-1,1c-0.7,1.3-0.7,3.2-0.7,5.3c0,2.3-0.2,10.5,0.5,11.6
+		c1.2,2,2.7,2,4.8,2c1.3,0,5.2,0,7.8,0c3.1,0,6.7,0,8.2,0.2c2.6,0.3,4.7,1,6.2,1.8c2.1,1.1,3.1,2.2,4.2,4.2c1.2,2.1,1.5,4.2,1.5,6.9
+		c0,2,0,4.7,0,6.9c0,2.5,0.1,7.1-0.3,9.6c-0.6,4.4-2.1,7.2-4.4,9.1c-1.7,1.4-3.8,2.3-6.7,2.6c-1.5,0.2-3.3,0.2-5.3,0.2
+		c-1.5,0-3.2,0-5,0c-6.6,0-14.2,0-20.9,0V94c12.2,0,20,0,20.8,0c1.6,0,6.1,0,7.1,0c2.1,0,3.1-0.1,4-1c0.9-0.8,1-2.2,1-4.2
+		c0-0.4,0-2.7,0-4.9c0-1.9,0-3.7,0-4.1c0-2.6,0.2-5.3-1.1-6.7C488,71.7,487.1,71.5,484.6,71.5z"/>
+</g>
+</svg>
diff --git a/docs/_static/theme_overrides.css b/docs/_static/theme_overrides.css
new file mode 100644
index 00000000..656b8ffe
--- /dev/null
+++ b/docs/_static/theme_overrides.css
@@ -0,0 +1,51 @@
+/* override table width restrictions */
+@media screen and (min-width: 767px) {
+
+    .wy-table-responsive table td {
+      /* !important prevents the common CSS stylesheets from overriding
+         this as on RTD they are loaded after this stylesheet */
+      white-space: normal !important;
+    }
+
+    .wy-table-responsive {
+      overflow: visible !important;
+    }
+  }
+
+  .wy-side-nav-search {
+    background-color: #e3e3e3 !important;
+  }
+
+  .wy-side-nav-search input[type=text] {
+     border-radius: 0px !important;
+     border-color: #333333 !important;
+  }
+
+  .icon-home {
+    color: #333333 !important;
+  }
+
+  .icon-home:hover {
+    background-color: #d6d6d6 !important;
+  }
+
+  .version {
+    color: #000000 !important;
+  }
+
+  a:hover {
+    color: #bd2c2a !important;
+  }
+
+  .logo {
+    width: 240px !important;
+  }
+
+  .highlight .c1 {
+      color: #008080;
+  }
+
+  .bolditalics {
+      font-weight: bold;
+      font-style: italic;
+  }
diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html
new file mode 100644
index 00000000..631d13d0
--- /dev/null
+++ b/docs/_templates/layout.html
@@ -0,0 +1,7 @@
+{% extends "!layout.html" %}
+
+{% block document %}
+<!--email_off-->
+    {{ super() }}
+<!--/email_off-->
+{% endblock %}
diff --git a/docs/abbr.md b/docs/abbr.md
deleted file mode 100644
index d4bb6eeb..00000000
--- a/docs/abbr.md
+++ /dev/null
@@ -1,1 +0,0 @@
-*[DUT]: Device under test
diff --git a/docs/api.rst b/docs/api.rst
new file mode 100644
index 00000000..71b63916
--- /dev/null
+++ b/docs/api.rst
@@ -0,0 +1,123 @@
+API Reference
+=============
+
+pytest-embedded
+---------------
+
+.. automodule:: pytest_embedded.app
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded.dut
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded.log
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded.unity
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded.utils
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+pytest-embedded-serial
+----------------------
+
+.. automodule:: pytest_embedded_serial.serial
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded_serial.dut
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+pytest-embedded-serial-esp
+--------------------------
+
+.. automodule:: pytest_embedded_serial_esp.serial
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+pytest-embedded-jtag
+--------------------
+
+.. automodule:: pytest_embedded_jtag.openocd
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded_jtag.gdb
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+pytest-embedded-idf
+-------------------
+
+.. automodule:: pytest_embedded_idf.app
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded_idf.serial
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded_idf.linux
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded_idf.dut
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded_idf.unity_tester
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+pytest-embedded-arduino
+-----------------------
+
+.. automodule:: pytest_embedded_arduino.app
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded_arduino.serial
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+pytest-embedded-qemu
+--------------------
+
+.. automodule:: pytest_embedded_qemu.app
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded_qemu.qemu
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+.. automodule:: pytest_embedded_qemu.dut
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 00000000..7d8432dd
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,39 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# For the full list of built-in configuration values, see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Project information -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
+project = 'pytest-embedded'
+project_homepage = 'https://github.com/espressif/pytest-embedded'
+copyright = '2023, Espressif Systems (Shanghai) Co., Ltd.'
+author = 'Fu Hanxi'
+
+# -- General configuration ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
+extensions = [
+    'myst_parser',
+    'sphinx.ext.autodoc',
+    'sphinx.ext.napoleon',
+    'sphinx_copybutton',
+    'sphinx_tabs.tabs',
+    'sphinxcontrib.mermaid',
+]
+
+templates_path = ['_templates']
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+# -- Options for HTML output -------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
+
+html_css_files = ['theme_overrides.css']
+html_logo = '_static/espressif-logo.svg'
+html_static_path = ['_static']
+html_theme = 'sphinx_rtd_theme'
+
+# mermaid 10.0.0 change the CDN path
+# temp workaround until sphinxcontrib-mermaid fixed it
+mermaid_version = '9.4.0'
diff --git a/docs/expecting.md b/docs/expecting.md
index f8e42878..45ab8f5e 100644
--- a/docs/expecting.md
+++ b/docs/expecting.md
@@ -14,84 +14,77 @@ All of these functions share these possible keyword arguments:
 
 If the pattern is `str` or `bytes`, it will convert to compiled regex with `bytes` and then run the function.
 
-!!! example
+```python
+import re
 
-    ```python
-    import re
+def test_basic_expect(dut):
+    dut.write('this would be redirected')
 
-    def test_basic_expect(dut):
-        dut.write('this would be redirected')
-
-        dut.expect(b'this')
-        dut.expect('would')
-        dut.expect('[be]{2}')
-        dut.expect(re.compile(b'redirected'))
-    ```
+    dut.expect(b'this')
+    dut.expect('would')
+    dut.expect('[be]{2}')
+    dut.expect(re.compile(b'redirected'))
+```
 
 If expecting success, the return value would be a `re.Match` object.
 
-!!! example
-
-    ```python
-    def test_expect_return_value(redirect, dut):
-        # here we use fixture `redirect` to write the sys.stdout to dut
-        with redirect():
-            print('this would be redirected')
+```python
+def test_expect_return_value(redirect, dut):
+    # here we use fixture `redirect` to write the sys.stdout to dut
+    with redirect():
+        print('this would be redirected')
 
-        res = dut.expect('this (would) be ([cdeirt]+)')
-        assert res.group() == b'this would be redirected'
-        assert res.group(1) == b'would'
-        assert res.group(2).decode('utf-8') == 'redirected'
-    ```
+    res = dut.expect('this (would) be ([cdeirt]+)')
+    assert res.group() == b'this would be redirected'
+    assert res.group(1) == b'would'
+    assert res.group(2).decode('utf-8') == 'redirected'
+```
 
 You can get the bytes read before timeout by expecting a `pexpect.TIMEOUT` object
 
-!!! example
+```python
+import time
+import threading
+import pexpect
 
-    ```python
-    import time
-    import threading
-    import pexpect
+def test_expect_from_eof(dut):
+    def write_bytes():
+        for _ in range(5):
+            dut.write('1')
+            time.sleep(2)
 
-    def test_expect_from_eof(dut):
-        def write_bytes():
-            for _ in range(5):
-                dut.write('1')
-                time.sleep(2)
+    write_thread = threading.Thread(target=write_bytes, daemon=True)
+    write_thread.start()
 
-        write_thread = threading.Thread(target=write_bytes, daemon=True)
-        write_thread.start()
-
-        res = dut.expect(pexpect.TIMEOUT, timeout=3)
-        assert res == b'11'
-    ```
+    res = dut.expect(pexpect.TIMEOUT, timeout=3)
+    assert res == b'11'
+```
 
 You can also get all bytes in the pexpect process buffer by expecting a `pexpect.EOF` object.
 
-!!! example
+```python
+import pexpect
 
-    ```python
-    import pexpect
+def test_expect_from_eof_current_buffer(dut):
+    dut.write('this would be redirected')
+    dut.expect('this')
 
-    def test_expect_from_eof_current_buffer(dut):
-        dut.write('this would be redirected')
-        dut.expect('this')
+    # close the pexpect process to generate an EOF
+    dut.pexpect_proc.terminate()
 
-        # close the pexpect process to generate an EOF
-        dut.pexpect_proc.terminate()
+    res = dut.expect(pexpect.EOF, timeout=None)
+    assert res == b' would be redirected'
+```
 
-        res = dut.expect(pexpect.EOF, timeout=None)
-        assert res == b' would be redirected'
-    ```
+```{eval-rst}
 
-!!! note
+.. note::
 
-    pexpect process would only read from the process into the buffer when running expecting functions.
+    The pexpect process would only read from the process into the buffer when running expecting functions.
     If you're expecting `pexpect.EOF` as the first statement, it would return an empty byte string
 
-    !!! example
+    .. code-block:: python
 
-        ```python
         import pexpect
 
         def test_expect_from_eof_at_first(dut):
@@ -102,28 +95,27 @@ You can also get all bytes in the pexpect process buffer by expecting a `pexpect
 
             res = dut.expect(pexpect.EOF, timeout=None)
             assert res == b''
-        ```
 
-What's more, argument `pattern` could be a list of all supported types.
+```
 
-!!! example
+What's more, argument `pattern` could be a list of all supported types.
 
-    ```python
-    import re
+```python
+import re
 
-    def test_expect_from_list(dut):
-        dut.write("this would be redirected")
+def test_expect_from_list(dut):
+    dut.write("this would be redirected")
 
-        pattern_list = [
-            "this",
-            b"would",
-            "[be]+",
-            re.compile(b"redirected"),
-        ]
+    pattern_list = [
+        "this",
+        b"would",
+        "[be]+",
+        re.compile(b"redirected"),
+    ]
 
-        for _ in range(4):
-            dut.expect(pattern_list)
-    ```
+    for _ in range(4):
+        dut.expect(pattern_list)
+```
 
 If you set `expect_all` to `True`, the `expect()` function would return with a list of returned values of each item.
 
@@ -133,32 +125,28 @@ If you set `expect_all` to `True`, the `expect()` function would return with a l
 
 If the pattern is `str`, would convert to `bytes` and then run the function.
 
-!!! example
-
-    ```python
-    def test_expect_exact(dut):
-        dut.write('this would be redirected')
-
-        dut.expect_exact('this would')
-        dut.expect_exact(b'be redirected')
-    ```
+```python
+def test_expect_exact(dut):
+    dut.write('this would be redirected')
 
-Same as [`expect(pattern, **kwargs)`][expectpattern-kwargs], argument `pattern` could be a list of all supported types.
+    dut.expect_exact('this would')
+    dut.expect_exact(b'be redirected')
+```
 
-!!! example
+Same as `expect(pattern, **kwargs)`, argument `pattern` could be a list of all supported types.
 
-    ```python
-    def test_expect_exact_from_list(dut):
-        dut.write('this would be redirected')
+```python
+def test_expect_exact_from_list(dut):
+    dut.write('this would be redirected')
 
-        pattern_list = [
-            'this would',
-            b'be redirected',
-        ]
+    pattern_list = [
+        'this would',
+        b'be redirected',
+    ]
 
-        for _ in range(2):
-            dut.expect_exact(pattern_list)
-    ```
+    for _ in range(2):
+        dut.expect_exact(pattern_list)
+```
 
 ## `expect_unity_test_output(**kwargs)`
 
@@ -170,52 +158,46 @@ When the test script ends, the DUT object would raise `AssertionError` if any un
 
 What's more, it would dump the junit report under a temp folder and would combine the junit report into the main one if you use `pytest --junitxml` feature.
 
-!!! example
-
-    ```python
-    import inspect
-    import pytest
-
-    def test_expect_unity_test_output_basic(dut):
-        dut.write(inspect.cleandoc('''
-            foo.c:100:test_case:FAIL:Expected 2 was 1
-            foo.c:101:test_case_2:FAIL:Expected 1 was 2
-            -------------------
-            2 Tests 2 Failures 0 Ignored
-            FAIL
-        '''))
-        with pytest.raises(AssertionError):
-            dut.expect_unity_test_output()
-
-        assert len(dut.testsuite.testcases) == 2
-        assert dut.testsuite.attrs['failures'] == 2
-        assert dut.testsuite.testcases[0].attrs['message'] == 'Expected 2 was 1'
-        assert dut.testsuite.testcases[1].attrs['message'] == 'Expected 1 was 2'
-    ```
+```python
+import inspect
+import pytest
+
+def test_expect_unity_test_output_basic(dut):
+    dut.write(inspect.cleandoc('''
+        foo.c:100:test_case:FAIL:Expected 2 was 1
+        foo.c:101:test_case_2:FAIL:Expected 1 was 2
+        -------------------
+        2 Tests 2 Failures 0 Ignored
+        FAIL
+    '''))
+    with pytest.raises(AssertionError):
+        dut.expect_unity_test_output()
+
+    assert len(dut.testsuite.testcases) == 2
+    assert dut.testsuite.attrs['failures'] == 2
+    assert dut.testsuite.testcases[0].attrs['message'] == 'Expected 2 was 1'
+    assert dut.testsuite.testcases[1].attrs['message'] == 'Expected 1 was 2'
+```
 
 It also supports [unity fixtures](https://github.com/ThrowTheSwitch/Unity/tree/master/extras/fixture) extra functionality
 
-!!! example
-
-    ```python
-    import inspect
-    import pytest
-
-    def test_expect_unity_test_output_fixture(dut):
-        dut.write(inspect.cleandoc('''
-            TEST(group, test_case)foo.c:100::FAIL:Expected 2 was 1
-            TEST(group, test_case_2)foo.c:101::FAIL:Expected 1 was 2
-            -------------------
-            2 Tests 2 Failures 0 Ignored
-            FAIL
-        '''))
-        with pytest.raises(AssertionError):
-            dut.expect_unity_test_output()
-
-        assert len(dut.testsuite.testcases) == 2
-        assert dut.testsuite.attrs['failures'] == 2
-        assert dut.testsuite.testcases[0].attrs['message'] == 'Expected 2 was 1'
-        assert dut.testsuite.testcases[1].attrs['message'] == 'Expected 1 was 2'
-    ```
-
---8<-- "docs/abbr.md"
+```python
+import inspect
+import pytest
+
+def test_expect_unity_test_output_fixture(dut):
+    dut.write(inspect.cleandoc('''
+        TEST(group, test_case)foo.c:100::FAIL:Expected 2 was 1
+        TEST(group, test_case_2)foo.c:101::FAIL:Expected 1 was 2
+        -------------------
+        2 Tests 2 Failures 0 Ignored
+        FAIL
+    '''))
+    with pytest.raises(AssertionError):
+        dut.expect_unity_test_output()
+
+    assert len(dut.testsuite.testcases) == 2
+    assert dut.testsuite.attrs['failures'] == 2
+    assert dut.testsuite.testcases[0].attrs['message'] == 'Expected 2 was 1'
+    assert dut.testsuite.testcases[1].attrs['message'] == 'Expected 1 was 2'
+```
diff --git a/docs/index.md b/docs/index.md
deleted file mode 100644
index 612c7a5e..00000000
--- a/docs/index.md
+++ /dev/null
@@ -1,1 +0,0 @@
---8<-- "README.md"
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 00000000..fa3e9ef4
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,20 @@
+.. pytest-embedded documentation master file, created by
+   sphinx-quickstart on Fri Jun  2 13:00:14 2023.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to pytest-embedded's documentation!
+===========================================
+
+.. include:: ../README.md
+   :parser: myst_parser.sphinx_
+
+.. toctree::
+   :hidden:
+
+   key_concepts
+   services
+   expecting
+   api
+   CHANGELOG
+   CONTRIBUTING
diff --git a/docs/key_concepts.md b/docs/key_concepts.md
index 074b1033..fd6db25d 100644
--- a/docs/key_concepts.md
+++ b/docs/key_concepts.md
@@ -7,33 +7,36 @@ Each test case would initialize a few fixtures. The most important fixtures are:
 - `msg_queue`, a message queue. There's a background listener process to read all the messages from this queue, log them to the terminal with an optional timestamp, and record them to the pexpect process.
 - `pexpect_proc`, a pexpect process, that could run `pexpect.expect()` for testing purposes.
 - `app`, the built binary
-- `dut`, a DUT test unit
+- `dut`, Device Under Test (DUT)
 
     A DUT would contain several daemon processes/threads. The output of each of them would be redirected to the `msg_queue` fixture.
 
-!!! note
+```{eval-rst}
+.. note::
 
     You may redirect any output to the `msg_queue` fixture by `contextlib.redirect_stdout`.
 
-    ```python
-    import contextlib
+    .. code-block:: python
 
-    def test_redirect(dut, msg_queue):
-        with contextlib.redirect_stdout(msg_queue):
-            print('will be redirected')
+        import contextlib
 
-        dut.expect_exact('redirected')
-    ```
+        def test_redirect(dut, msg_queue):
+            with contextlib.redirect_stdout(msg_queue):
+                print('will be redirected')
+
+            dut.expect_exact('redirected')
 
     Or you may redirect the output from a fixture `redirect`
 
-    ```python
-    def test_redirect(dut, msg_queue, redirect):
-        with redirect():
-            print('will also be redirected')
+    .. code-block:: python
+
+        def test_redirect(dut, msg_queue, redirect):
+            with redirect():
+                print('will also be redirected')
+
+            dut.expect_exact('redirected')
 
-        dut.expect_exact('redirected')
-    ```
+```
 
 You can run `pytest --fixtures` to get all the fixtures defined with `pytest-embedded`. They are under the section `fixtures defined from pytest_embedded.plugin`.
 
@@ -41,35 +44,29 @@ You can run `pytest --fixtures` to get all the fixtures defined with `pytest-emb
 
 All the CLI options support parametrization via `indirect=True`. Parametrization is a feature provided by `pytest`, please refer to [Parametrizing tests](https://docs.pytest.org/en/latest/example/parametrize.html) for its documentation.
 
-!!! example
-
-    Running a test script like:
-
-    ```python
-    @pytest.mark.parametrize(
-        'embedded_services, app_path',
-        [
-            ('idf', app_path_1),
-            ('idf', app_path_2),
-        ],
-        indirect=True,
-    )
-    def test_serial_tcp(dut):
-        assert dut.app.target == 'esp32'
-        dut.expect('Restart now')
-    ```
-
-    with shell command `pytest`
-
-    is equivalent to running a test script like:
-
-    ```python
-    def test_serial_tcp(dut):
-        assert dut.app.target == 'esp32'
-        dut.expect('Restart now')
-    ```
-
-    with two shell commands `pytest --embedded-services idf --app-path <app_path_1>` and `pytest --embedded-services idf --app-path <app_path_2>`
+For example, running shell command `pytest` with the test script:
+
+```python
+@pytest.mark.parametrize(
+    'embedded_services, app_path',
+    [
+        ('idf', app_path_1),
+        ('idf', app_path_2),
+    ],
+    indirect=True,
+)
+def test_serial_tcp(dut):
+    assert dut.app.target == 'esp32'
+    dut.expect('Restart now')
+```
+
+is equivalent to running two shell commands `pytest --embedded-services idf --app-path <app_path_1>` and `pytest --embedded-services idf --app-path <app_path_2>` with the test script:
+
+```python
+def test_serial_tcp(dut):
+    assert dut.app.target == 'esp32'
+    dut.expect('Restart now')
+```
 
 ## Services
 
@@ -94,64 +91,58 @@ Here are a few examples of how to enable this. For detailed information, please
 
 After you enabled the multi-dut mode, all the fixtures would be a tuple with instances. Each instance inside the tuple would be independent. For parametrization, each configuration will use `|` as a separator for each instance.
 
-!!! example
+For example, running shell command:
 
-    ```shell
-    pytest \
-    --embedded-services serial|serial \
-    --count 2 \
-    --app-path <master_bin>|<slave_bin>
-    ```
+```shell
+pytest \
+--embedded-services serial|serial \
+--count 2 \
+--app-path <master_bin>|<slave_bin>
+```
 
-    In this example, `app` would be a tuple of 2 `App` instances, and `dut` would be a tuple of 2 `Dut` Instances.
+would enable 2 DUTs with `serial` service. `app` would be a tuple of 2 `App` instances, and `dut` would be a tuple of 2 `Dut` Instances.
 
-    You can test with:
+You can test with:
 
-    ```python
-    def test(dut):
-        master = dut[0]
-        slave = dut[1]
+```python
+def test(dut):
+    master = dut[0]
+    slave = dut[1]
 
-        master.expect('sent')
-        slave.expect('received')
-    ```
+    master.expect('sent')
+    slave.expect('received')
+```
 
 ### Specify once when applying to all DUTs
 
 If all DUTs share the same configuration value, you can specify only once.
 
-!!! example
+```shell
+pytest \
+--embedded-services serial \
+--count 2 \
+--app-path <master_bin>|<slave_bin>
+```
 
-    ```shell
-    pytest \
-    --embedded-services serial \
-    --count 2 \
-    --app-path <master_bin>|<slave_bin> \
-    ```
-
-    `--embedded-services serial` would apply to all DUTs
+`--embedded-services serial` would apply to all DUTs
 
 ### Vacant Value if it's Useless
 
 Sometimes one option is only useful when enabling specific services. You can set a vacant value if this config is only useful for certain DUTs.
 
-!!! example
-
-    ```shell
-    pytest \
-    --embedded-services qemu|serial \
-    --count 2 \
-    --app-path <master_bin>|<slave_bin> \
-    --qemu-cli-args "<args>|" \
-    --port "|<port>" \
-    ```
+```shell
+pytest \
+--embedded-services qemu|serial \
+--count 2 \
+--app-path <master_bin>|<slave_bin> \
+--qemu-cli-args "<args>|" \
+--port "|<port>"
+```
 
-    `--qemu-cli-args` would apply to the first DUT with `qemu` service and `--port` would apply to the second DUT with `serial` service.
+`--qemu-cli-args` would apply to the first DUT with `qemu` service and `--port` would apply to the second DUT with `serial` service.
 
 ## Logging
 
 `pytest-embedded` print all the DUT output with the timestamp. If you want to remove the timestamp, please run pytest with `pytest --with-timestamp n` to disable this feature.
 
 By default, `pytest` would swallow the stdout. If you want to check the live output, please run pytest with `pytest -s`.
-
---8<-- "docs/abbr.md"
diff --git a/docs/requirements.txt b/docs/requirements.txt
index fefa247a..faa5c6be 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,6 +1,11 @@
 -r ../requirements.txt
-mkdocs
-mkdocstrings[python]
-mkdocs-material
-plantuml-markdown
-griffe<0.27.4
+
+# theme
+sphinx-rtd-theme
+
+# extensions
+myst-parser              # markdown support
+sphinx-tabs              # tabs support
+sphinx_copybutton        # copy button
+sphinxcontrib-mermaid    # mermaid graph support
+sphinxcontrib-napoleon   # google style docstring support
diff --git a/docs/services.md b/docs/services.md
index 8d9a1280..52409736 100644
--- a/docs/services.md
+++ b/docs/services.md
@@ -12,21 +12,27 @@ Please refer to the instructions under the "embedded" or "embedded-[SERVICE]" gr
 
 Activate a service would enable a set of fixtures or add some extra functionalities to a few fixtures.
 
---8<-- "pytest-embedded-serial/README.md"
+```{include} ../pytest-embedded-serial/README.md
+```
 
---8<-- "pytest-embedded-serial-esp/README.md"
+```{include} ../pytest-embedded-serial-esp/README.md
+```
 
---8<-- "pytest-embedded-idf/README.md"
+```{include} ../pytest-embedded-idf/README.md
+```
 
---8<-- "pytest-embedded-jtag/README.md"
+```{include} ../pytest-embedded-jtag/README.md
+```
 
---8<-- "pytest-embedded-qemu/README.md"
+```{include} ../pytest-embedded-qemu/README.md
+```
 
---8<-- "pytest-embedded-arduino/README.md"
+```{include} ../pytest-embedded-arduino/README.md
+```
 
 ## Dependency Graph
 
-```mermaid
+```{mermaid}
 graph LR
     pytest_embedded --> pytest_embedded_serial
     pytest_embedded -->|pytest_embedded_serial_esp is an optional dependency| pytest_embedded_idf
@@ -36,5 +42,3 @@ graph LR
     pytest_embedded_serial --> pytest_embedded_serial_esp
     pytest_embedded_serial --> pytest_embedded_jtag
 ```
-
---8<-- "docs/abbr.md"


Test patch:
diff --git a/.github/workflows/test-build-docs.yml b/.github/workflows/test-build-docs.yml
index 67fd848d..4e619c32 100644
--- a/.github/workflows/test-build-docs.yml
+++ b/.github/workflows/test-build-docs.yml
@@ -24,4 +24,4 @@ jobs:
           pip install -r docs/requirements.txt
       - name: Test build docs
         run: |
-          mkdocs build
+          cd docs && make html SPHINXOPTS="-W"
diff --git a/docs/references/pytest_embedded.md b/docs/references/pytest_embedded.md
deleted file mode 100644
index a228ec6a..00000000
--- a/docs/references/pytest_embedded.md
+++ /dev/null
@@ -1,6 +0,0 @@
-::: pytest_embedded.app
-::: pytest_embedded.dut
-::: pytest_embedded.log
-::: pytest_embedded.utils
-::: pytest_embedded.unity
-::: pytest_embedded.plugin
diff --git a/docs/references/pytest_embedded_arduino.md b/docs/references/pytest_embedded_arduino.md
deleted file mode 100644
index 9f0d6346..00000000
--- a/docs/references/pytest_embedded_arduino.md
+++ /dev/null
@@ -1,2 +0,0 @@
-::: pytest_embedded_arduino.app
-::: pytest_embedded_arduino.serial
diff --git a/docs/references/pytest_embedded_idf.md b/docs/references/pytest_embedded_idf.md
deleted file mode 100644
index 139c9d85..00000000
--- a/docs/references/pytest_embedded_idf.md
+++ /dev/null
@@ -1,4 +0,0 @@
-::: pytest_embedded_idf.app
-::: pytest_embedded_idf.serial
-::: pytest_embedded_idf.dut
-::: pytest_embedded_idf.unity_tester
diff --git a/docs/references/pytest_embedded_jtag.md b/docs/references/pytest_embedded_jtag.md
deleted file mode 100644
index dfbbcd60..00000000
--- a/docs/references/pytest_embedded_jtag.md
+++ /dev/null
@@ -1,2 +0,0 @@
-::: pytest_embedded_jtag.gdb
-::: pytest_embedded_jtag.openocd
diff --git a/docs/references/pytest_embedded_qemu.md b/docs/references/pytest_embedded_qemu.md
deleted file mode 100644
index 0e42fcda..00000000
--- a/docs/references/pytest_embedded_qemu.md
+++ /dev/null
@@ -1,3 +0,0 @@
-::: pytest_embedded_qemu.app
-::: pytest_embedded_qemu.qemu
-::: pytest_embedded_qemu.dut
diff --git a/docs/references/pytest_embedded_serial.md b/docs/references/pytest_embedded_serial.md
deleted file mode 100644
index 58b305b6..00000000
--- a/docs/references/pytest_embedded_serial.md
+++ /dev/null
@@ -1,2 +0,0 @@
-::: pytest_embedded_serial.serial
-::: pytest_embedded_serial.dut
diff --git a/docs/references/pytest_embedded_serial_esp.md b/docs/references/pytest_embedded_serial_esp.md
deleted file mode 100644
index cf9baaa7..00000000
--- a/docs/references/pytest_embedded_serial_esp.md
+++ /dev/null
@@ -1,1 +0,0 @@
-::: pytest_embedded_serial_esp.serial
diff --git a/pytest-embedded-arduino/README.md b/pytest-embedded-arduino/README.md
index f6b22fb3..a6a87037 100644
--- a/pytest-embedded-arduino/README.md
+++ b/pytest-embedded-arduino/README.md
@@ -4,11 +4,15 @@ pytest embedded service for Arduino project
 
 Extra Functionalities:
 
-=== "`pytest-embedded-serial-esp` activated"
+```{eval-rst}
+.. tabs::
 
-    - `app`: Parse Arduino's build directory and gather more information.
-    - `serial`: Auto flash the built binary into the target board at the beginning when running test cases.
+   .. group-tab:: `pytest-embedded-serial-esp` activated
 
-=== "`pytest-embedded-serial-esp` NOT activated"
+        - `app`: Parse Arduino's build directory and gather more information.
+        - `serial`: Auto flash the built binary into the target board at the beginning when running test cases.
 
-    - `app`: Parse Arduino's build directory and gather more information.
+   .. group-tab:: `pytest-embedded-serial-esp` NOT activated
+
+        - `app`: Parse Arduino's build directory and gather more information.
+```
diff --git a/pytest-embedded-arduino/pytest_embedded_arduino/app.py b/pytest-embedded-arduino/pytest_embedded_arduino/app.py
index 035bda72..6111f338 100644
--- a/pytest-embedded-arduino/pytest_embedded_arduino/app.py
+++ b/pytest-embedded-arduino/pytest_embedded_arduino/app.py
@@ -14,11 +14,12 @@ class ArduinoApp(App):
         fqbn (str): Fully Qualified Board Name.
         target (str) : ESPxx chip.
         flash_files (List[Tuple[int, str, str]]): List of (offset, file path, encrypted) of files need to be flashed in.
-        flash_settings (dict[str, Any]): dict of flash settings
-        binary_offsets (dict[str, List[int, int, int]]): dict of binaries' offset.
     """
 
+    #: dict of flash settings
     flash_settings = {'flash_mode': 'dio', 'flash_size': 'detect', 'flash_freq': '80m'}
+
+    #: dict of binaries' offset.
     binary_offsets = {
         'esp32': [0x1000, 0x8000, 0x10000],
         'esp32s2': [0x1000, 0x8000, 0x10000],
diff --git a/pytest-embedded-idf/README.md b/pytest-embedded-idf/README.md
index dc38764e..29f9ef19 100644
--- a/pytest-embedded-idf/README.md
+++ b/pytest-embedded-idf/README.md
@@ -4,15 +4,15 @@ pytest embedded service for esp-idf project
 
 Extra Functionalities:
 
-=== "`pytest-embedded-serial-esp` activated"
+```{eval-rst}
+.. tabs::
 
-    - `app`: parse the built binary by idf rules and gather more information.
-    - `serial`: auto flash the built binary into the target board at the beginning when running test cases.
+   .. group-tab:: `pytest-embedded-serial-esp` activated
 
-=== "`pytest-embedded-serial-esp` NOT activated"
+        - `app`: parse the built binary by idf rules and gather more information.
+        - `serial`: auto flash the built binary into the target board at the beginning when running test cases.
 
-    - `app`: parse the built binary by idf rules and gather more information.
+   .. group-tab:: `pytest-embedded-serial-esp` NOT activated
 
-Used CLI Options:
-
-- `part-tool`
+        - `app`: parse the built binary by idf rules and gather more information.
+```
diff --git a/pytest-embedded-idf/pytest_embedded_idf/dut.py b/pytest-embedded-idf/pytest_embedded_idf/dut.py
index 3c874436..31ce5262 100644
--- a/pytest-embedded-idf/pytest_embedded_idf/dut.py
+++ b/pytest-embedded-idf/pytest_embedded_idf/dut.py
@@ -144,7 +144,7 @@ def _check_coredump(self) -> None:
         For UART and panic output, would read the `_pexpect_logfile` file.
         For partition, would read the flash according to the partition table. needs a valid `parttool_path`.
 
-        Notes:
+        Note:
             - May include multiple core dumps, since each test case may include several unity test cases.
             - May have duplicated core dumps, since after the core dump happened, the target chip would reboot
             automatically.
diff --git a/pytest-embedded-idf/pytest_embedded_idf/unity_tester.py b/pytest-embedded-idf/pytest_embedded_idf/unity_tester.py
index 43ac3daf..37377de6 100644
--- a/pytest-embedded-idf/pytest_embedded_idf/unity_tester.py
+++ b/pytest-embedded-idf/pytest_embedded_idf/unity_tester.py
@@ -38,24 +38,21 @@
 class UnittestMenuCase:
     """
     Dataclass of esp-idf unit test cases parsed from test menu
-
-    Attributes:
-        index: The index of the case, which can be used to run this case.
-        name: The name of the case.
-        type: Type of this case, which can be `normal` `multi_stage` or `multi_device`.
-        keywords: List of additional keywords of this case. For now, we have `disable` and `ignore`.
-        groups: List of groups of this case, this is usually the component which this case belongs to.
-        attributes: Dict of attributes of this case, which is used to describe timeout duration,
-            test environment, etc.
-        subcases: List of dict of subcases of this case, if this case is a `multi_stage` or `multi_device` one.
     """
 
+    #: The index of the case, which can be used to run this case.
     index: int
+    #: The name of the case.
     name: str
+    #: Type of this case, which can be `normal` `multi_stage` or `multi_device`.
     type: str
+    #: List of additional keywords of this case. For now, we have `disable` and `ignore`.
     keywords: t.List[str]
+    #: List of groups of this case, this is usually the component which this case belongs to.
     groups: t.List[str]
+    #: Dict of attributes of this case, which is used to describe timeout duration,
     attributes: t.Dict[str, t.Any]
+    #: List of dict of subcases of this case, if this case is a `multi_stage` or `multi_device` one.
     subcases: t.List[t.Dict[str, t.Any]]
 
     @property
@@ -239,7 +236,7 @@ def _record_single_unity_test_case(func):
         """
         The first argument of the function that is using this decorator must be `case`. passing with args.
 
-        Notes:
+        Note:
             This function is better than `dut.expect_unity_output()` since it will record the test case even it core
                 dumped during running the test case or other reasons that cause the final result block is uncaught.
         """
@@ -331,7 +328,7 @@ def _run_normal_case(
         """
         Run a specific normal case
 
-        Notes:
+        Note:
             Will skip with a warning if the case type is not "normal"
 
         Args:
@@ -356,7 +353,7 @@ def _run_multi_stage_case(
         """
         Run a specific multi_stage case
 
-        Notes:
+        Note:
             Will skip with a warning if the case type is not "multi_stage"
 
         Args:
@@ -402,7 +399,7 @@ def run_all_single_board_cases(
         run_ignore_cases: bool = False,
     ) -> None:
         """
-        Run all multi_stage cases
+        Run all single board cases, including multi_stage cases, and normal cases
 
         Args:
             group: test case group
@@ -596,7 +593,7 @@ def run_multi_dev_case(
         """
         Run a specific multi_device case
 
-        Notes:
+        Note:
             Will skip with a warning if the case type is not multi_device
 
         Args:
diff --git a/pytest-embedded-jtag/README.md b/pytest-embedded-jtag/README.md
index f5821449..8f532d97 100644
--- a/pytest-embedded-jtag/README.md
+++ b/pytest-embedded-jtag/README.md
@@ -7,10 +7,3 @@ Extra Functionalities:
 - `openocd`: enable the fixture
 - `gdb`: enable the fixture
 - `dut`: duplicate the `openocd` output and the `gdb` output to `dut.pexpect_proc`.
-
-Used CLI Options:
-
-- `gdb-prog-path`
-- `gdb-cli-args`
-- `openocd-prog-path`
-- `openocd-cli-args`
diff --git a/pytest-embedded-qemu/CHANGELOG.md b/pytest-embedded-qemu/CHANGELOG.md
deleted file mode 100644
index ef43be90..00000000
--- a/pytest-embedded-qemu/CHANGELOG.md
+++ /dev/null
@@ -1,623 +0,0 @@
-## v1.3.0 (2023-05-31)
-
-### Feat
-
-- **idf**: `IdfApp` add new property `is_xtensa`, `is_riscv32`
-- **idf**: add `confirm_write` method to make sure the write happens
-- **idf**: add `run_single_board_case` function
-- **idf**: expose pytest_embedded_idf.UnittestMenuCase
-- **qemu**: support flash encryption workflow
-- **qemu**: support riscv32 targets, remove target restriction
-
-### Fix
-
-- **qemu**: Add more idf non-target related functions when enabling service `idf`
-
-## v1.2.5 (2023-04-11)
-
-### Feat
-
-- add log in system-out
-
-### Fix
-
-- **idf**: lazy import optional dependencies
-- **idf**: skip decode panic output when no elf file
-
-## v1.2.4 (2023-03-10)
-
-### Feat
-
-- **idf**: support loadable app with bin file
-
-### Fix
-
-- illegal xml chars for all types of test cases
-- remove 1.1 added unused argument `start_time`
-
-### Refactor
-
-- improve logging and use "shell=False" in `DuplicateStdoutPopen`
-- refine errors and warnings
-
-## v1.2.3 (2023-02-06)
-
-### Fix
-
-- illegal xml chars
-- support rfc2217 connections
-
-## v1.2.2 (2023-01-29)
-
-### Fix
-
-- teardown skipped when setting up period failed for multi-dut
-- leaking file descriptors
-
-## v1.2.1 (2023-01-05)
-
-### Fix
-
-- **idf**: `LinuxDut.write` failed since `serial` is `None`
-
-## v1.2.0 (2022-12-29)
-
-### Feat
-
-- support running tests on linux targets
-
-### Fix
-
-- ImportError when install `pytest_embedded` only
-
-## v1.1.1 (2022-12-08)
-
-### Fix
-
-- **idf**: load_ram could use `flash_project_args` file as well
-
-## v1.1.0 (2022-12-05)
-
-### Feat
-
-- **idf**: add fixture `unity_tester` to test multi device unity test cases (beta)
-- add "--check-duplicates" cli option
-- **idf**: add `run_all_single_board_cases()` to `IdfDut`
-- Added tests for testcase runner. Added unity test execution time into JUnit report
-- add a script for supporting multi-dev and multi-stage case from idf to pytest embedded
-- **jtag**: add cli option `--no-gdb` to stop create gdb instance automatically
-
-### Fix
-
-- **idf**: erase_all when hit the port-app cache cause no binary got flashed
-- **idf**: set correct toolchain prefix for RISC-V targets
-- Added test case name uniqueness validation
-- create openocd/gdb processes earlier than app/serial
-
-## v1.0.2 (2022-11-07)
-
-### Fix
-
-- **jtag**: stop redirecting openocd output to pexpect process
-- **esp**: drop port-app cache when `erase_flash`
-- **esp**: erase flash before `flash` when set `--erase-all`
-
-### Refactor
-
-- rewrite set/drop/hit cache code
-
-## v1.0.1 (2022-10-28)
-
-### Feat
-
-- support set file extension of the log file
-
-### Fix
-
-- `_meta.logdir` should be the `test_case_tempdir`, change to function-scope
-- remove unused cli option "--reorder-by-app-path"
-
-## v1.0.0 (2022-10-20)
-
-### Fix
-
-- **idf**: use empty list or dict for default values
-- **serial**: improve logging when _event_loop run into unknown error
-
-## v1.0.0b3 (2022-10-20)
-
-### Fix
-
-- **serial**: use thread for serial
-
-## v1.0.0b2 (2022-10-18)
-
-### Fix
-
-- multi-dut \r\n messed-up the log
-- **serial**: event loop with interval seconds
-- **esp**: add missing redirect lines
-- **esp**: `hard_reset` not working
-
-## v1.0.0b1 (2022-10-14)
-
-### Feat
-
-- make DuplicateStdoutPopen logs under `session_tmpdir`
-
-### Fix
-
-- improve teardown logic
-- **jtag**: `openocd` instance redirect stdout to pexpect proc
-
-## v1.0.0b0 (2022-10-13)
-
-### Feat
-
-- add fixture `port_target_cache`, `port_app_cache`
-
-### Fix
-
-- add micro second digits to `session_tempdir`
-- revert the breaking change to rename `dut.pexpect_proc` to `dut._p`
-
-## v1.0.0a2 (2022-10-12)
-
-### Feat
-
-- **esp**: support bind ports by esptool read_mac
-- Added support for binding ports based on the USB device location
-- **esp**: support loadable elf
-- **esp**: add `EsptoolArgs` to `EspSerial`
-- **esp**: `use_esptool` new argument `hard_reset_after` and `no_stub`
-- `live_print_call` could accept kwargs `msg_queue` and `expect_returncode`
-- **jtag**: support loadable elf
-- blocking io gdb write
-- **jtag**: flash via JTAG accept port-app-cache
-- improve logging in popen objects
-- **jtag**: flash via JTAG
-- add fixture dut_index and dut_total
-
-### Fix
-
-- **serial**: remove occupied ports from the list of available ports
-- **qemu**: wrong init args
-- kill processes generated inside popen afterwards
-
-### Refactor
-
-- **jtag**: remove `JtagDut`. use `SerialDut` or its subclasses instead
-- **serial**: rename `start_redirect_serial_process` to `start_redirect_process`
-
-## v1.0.0a1 (2022-09-22)
-
-### Fix
-
-- **serial**: event loop for interacting with `serial.proc`
-
-## v1.0.0a0 (2022-09-20)
-
-### Feat
-
-- **esp**: Support esptool>4.0 only
-- add fixture `session_root_logdir` and cli option `--root-logdir`
-- added panic handler support
-
-### Fix
-
-- **serial**: skip creating a new one if exists when `start_redirect_serial_process`
-- **esp**: file stream keep open when exception is raised
-- remove `autouse` for fixture `session_tempdir`
-
-### Refactor
-
-- use multiprocessing.Process with Queue
-
-## v0.8.2 (2022-08-23)
-
-### Fix
-
-- **esp**: UnboundLocalError when specify beta_target and port together
-
-## v0.8.1 (2022-08-22)
-
-### Fix
-
-- parse error when unity test case name with colon
-
-## v0.8.0 (2022-08-22)
-
-### Feat
-
-- **esp**: support `beta_target`
-
-### Fix
-
-- **idf**: parse_unity_menu subcases index to int
-
-## v0.7.10 (2022-08-04)
-
-### Fix
-
-- multi dut with single junit report
-
-## v0.7.9 (2022-08-02)
-
-### Fix
-
-- merge junit report error when multi-dut
-
-## v0.7.8 (2022-07-28)
-
-### Feat
-
-- **idf**: add function to parse case list from unit test menu
-
-### Fix
-
-- remove asci color code in logging
-
-## v0.7.7 (2022-07-26)
-
-### Fix
-
-- wrong xml detected when `_session_tempdir` stash is not initialized
-- issue when fixture value is not str
-
-## v0.7.6 (2022-07-25)
-
-### Feat
-
-- **esp**: add fixture `esptool_baud`
-- **serial**: move fixture `baud` to service `serial`
-
-### Fix
-
-- record session_tempdir into config.stash
-
-## v0.7.5 (2022-07-11)
-
-### Fix
-
-- cli option "--erase-flash" conflict with function `erase_flash()`
-
-## v0.7.4 (2022-07-11)
-
-### Fix
-
-- use fixed major version instead of major.minor
-- port occupied before init finished
-
-## v0.7.3 (2022-06-06)
-
-## v0.7.2 (2022-06-01)
-
-### Fix
-
-- loose esptool version dependency. remove the upper limit
-
-## v0.7.1 (2022-05-25)
-
-### Feat
-
-- **idf**: add cli option "--skip-check-coredump"
-
-### Fix
-
-- **idf**: make elf file detection optional
-
-## v0.7.0 (2022-05-09)
-
-### Fix
-
-- **idf**: KeyError when not enabled coredump related configs
-
-## v0.7.0rc3 (2022-05-07)
-
-### Fix
-
-- **esp**: remove cryptography version limit
-
-## v0.7.0rc2 (2022-05-07)
-
-### Fix
-
-- improve debug string
-- **idf**: flash with non-iterable empty files
-
-## v0.7.0rc1 (2022-04-25)
-
-### Feat
-
-- extract method `parse_multi_dut_args`
-
-## v0.7.0rc0 (2022-04-15)
-
-### Feat
-
-- support 3.7+ python
-- **idf**: parse coredump when teardown dut
-- **serial**: add stop_redirect_thread method and disable_redirect_thread context manager
-- **idf**: add cli option "--erase-nvs"
-- **idf**: use flasher_args.json to flash files. Require less files
-
-### Fix
-
-- **esp**: fix requirements for rpi
-- **serial**: use other serial type as well
-
-## v0.6.2 (2022-03-18)
-
-### Feat
-
-- add `expect_all` keyword for `expect` and `expect_exact` functions
-
-### Fix
-
-- **esp**: stubbed loader can never read serial because of redirect_io_thread
-
-## v0.6.1 (2022-03-18)
-
-### Fix
-
-- **esp**: esptool wrong boot mode issue
-- use typing.Pattern instead of re.Pattern
-- **esp**: sort ports before auto detect port target
-
-## v0.6.0 (2022-03-08)
-
-## v0.6.0rc1 (2022-03-04)
-
-### Feat
-
-- **esp**: add `hard_reset` method
-
-### Fix
-
-- `apply_count` for non-iterable items
-
-## v0.6.0rc0 (2022-02-22)
-
-### Feat
-
-- **idf**: add --confirm-target-elf-sha256 cli option
-- **idf**: cache port and app
-- **esp**: cache port and target
-- use pytest>=7.0
-- extract the binary_path to the `App` class
-- cache port/target, port/app_path_build_dir if possible. use pytest 7.0 feature stash
-- **idf**: add flashed elf related methods in `IdfSerial`
-- **idf**: add attr `bin_file` in IdfApp
-- log the log folders while setup for test case
-- **idf**: add `dump_flash` method in `IdfSerial`
-- **idf**: add IdfDut class
-- **esp**: add use_esptool decorator to auto connect before and hard reset after
-- the logging.error would turn to be derived error
-
-### Fix
-
-- unity case name now could include spaces
-- would raise correctly when `expect` a list pattern failed
-- **jtag**: -gdb-set could pass multiple args
-- **log**: multi-dut now would print source prefix
-- **log**: flush to sys.stdout instantly
-
-### Refactor
-
-- add function _request_param_or_config_option_or_default for fixtures
-
-## v0.5.1 (2022-01-21)
-
-### Fix
-
-- **serial**: write to pexpect process until '\n'
-
-## v0.5.0 (2022-01-21)
-
-### Feat
-
-- fail at the end if unity test case failed, try run more tests
-- set expect_unity_test_output timeout to 60s
-
-## v0.5.0rc1 (2022-01-18)
-
-### Feat
-
-- **unity**: add argument `extra_before` to `expect_unity_test_output`
-- **qemu**: default image path move to `APP_PATH/BUILD_DIR/IMAGE_NAME`
-- **log**: thread-safe print instead of logging.info
-- remove `expect_list`
-- add `Dut.expect_unity_test_output` method
-- make import pytest-embedded easier
-- show log file location when expect functions failed
-- add fixture `test_case_tempdir`
-
-### Fix
-
-- raise idf app not parsable error if binary path not parsable
-- **qemu**: re-generate qemu image. add cli option "--skip-regenerate_image"
-- **jtag**: use real file logging instead of pipe
-- ensure use default value set in class if not specified in cli or param
-- **qemu**: `dut.write` to qemu process correctly
-- **serial**: `dut.write()` could accept `str` data, auto add `\n` ending
-
-## v0.5.0rc0 (2022-01-06)
-
-### Feat
-
-- **qemu**: remove cli option `qemu-log-path`
-- **log**: log buffer as error when no matched pattern while expecting
-- **log**: remove `source` argument in all `DuplicatedStdout` related functions
-- Add Arduino service.
-- **esp**: use suggest flash baudrate if lower
-- support windows by using real file and fdspawn
-- **esp**: use env var `ESPPORT` for "--port" and `ESPBAUD` for "--baud"
-- **esp**: simplify esptool call process
-
-### Fix
-
-- setup.py regex issue with prerelease version
-- **esp**: different dut will not use the same ports anymore
-- make setup.py installable on windows
-- multiprocessing pickle error on macos
-- make the hook function to apply parallel count as the last step
-
-### Refactor
-
-- Move skip_autoflash to EspSerial.
-- Move "build_dir" option to the base group since it's also used by other apps.
-- replace multiprocessing with thread
-- make `pexpect_proc` as required for most of the classes
-
-## v0.4.5 (2021-11-29)
-
-### Feat
-
-- real-time logging
-- add `expect_list` and `expect_exact`
-- add parallel run cli options
-
-### Fix
-
-- hard reset when skipping auto flash
-
-## v0.4.4 (2021-11-18)
-
-### Feat
-
-- **base**: use the folder where `test_file_path` locates as the default `app_path`
-- **idf**: replace parse binary config from sdkconfig to sdkconfig.json
-- **idf**: add cli option "build_dir"
-
-### Fix
-
-- **qemu**: add the missing `build_dir` to the parent `IdfApp`
-- use realpath instead
-
-## v0.4.3 (2021-11-16)
-
-### Feat
-
-- return `re.Match` if `dut.expect()` succeeded
-- **idf**: add option `--skip-autoflash`
-
-## v0.4.2 (2021-10-25)
-
-### Feat
-
-- add dut count at the start of each line
-
-### Fix
-
-- add version limit or armv71(rpi)
-
-## v0.4.1 (2021-08-26)
-
-### Fix
-
-- run close method only when initialized correctly
-- correct the error message when service required package not installed
-- pexpect process would echo the input, set echo to off
-
-## v0.4.0 (2021-08-25)
-
-### Feat
-
-- add multi DUT support, use "count" option to duplicate fixtures
-- use "embedded-services" option to extend functionalities instead of activating plugins.
-
-### Fix
-
-- create folder failed when specifying a file under current folder
-
-## v0.3.2 (2021-08-12)
-
-### Fix
-
-- **jtag**: do not import idf package
-
-## v0.3.1 (2021-07-09)
-
-### Fix
-
-- **idf**: optional dependency with `pytest-embedded-serial-esp` while import
-
-## v0.3.0 (2021-07-06)
-
-### Feat
-
-- **qemu**: rename package to `pytest-embedded-qemu`, with optional dependency `pytest-embedded-idf`
-- **jtag**: add dependency `pytest-embedded-serial`, remove optional requirements `pytest-embedded-serial-esp`
-- **idf**: rename module `dut` to `serial`, override `serial` if satisfy the optional dependency
-- **esp**: rename module `dut` to `serial`, override `serial` fixture
-- **serial**: extract serial into a standalone fixture
-- **base**: Add fixture `pexpect_proc`
-
-## v0.2.0 (2021-06-29)
-
-### Feat
-
-- **jtag**: add jtag support
-- **log**: add LivePrintPopen custom Popen class
-- **serial**: make serial port could be overridden by parameterization
-- **qemu**: `qemu_cli_args` and `qemu_extra_args` now can be set via cli and override via parameterization
-
-### Fix
-
-- **log**: use rstrip instead of strip to keep the logs' indentation
-
-## v0.1.1 (2021-06-16)
-
-### Feat
-
-- **qemu**: move `qemu_cli_args` and `qemu_extra_args` from cli args to parametrize option
-- **qemu**: check image_path exist or not and target chip type while running
-
-### Fix
-
-- **test**: remove local dir
-- **idf**: add base dependency
-
-## v0.1.0 (2021-06-11)
-
-### Feat
-
-- **qemu**: Move qemu to single fixture
-- **base**: fixture `redirect` could have argument `source`
-- **base**: add plugin `redirect` to duplicate and redirect `sys.stdout`
-- duplicate stdout to file descriptor
-- rename serial_dut/serial_esp to dut to keep consistency
-- **qemu-idf**: add qemu-idf
-- **idf**: make serial/esp dependencies optional
-- move redirect stdout to pexpect process into a reusable decorator
-- **idf**: simplify flash files, encrypt files parsing
-- **serial_esp**: move esp related serial into a standalone plugin
-- re-organize code. move esp related serial into embedded-serial-esp
-- **print**: redirect esptool print to pexpect
-- **idf**: move the flash specific code into idf plugin
-- **serial**: move serial related into serial plugin
-- **idf**: move idf related app into IDFApp
-- **serial**: support flash by serial
-- **base**: App support encrypt files
-- **serial**: support pexpect from serial
-- **base**: class App read target from sdkconfig file
-- **base**: add App class
-- update feature list after review
-- writing feature list and some initial design
-- rename to pytest-embedded
-- re-organize the file structure
-- use multi plugins to attach the method to the DUT class
-- use dynamic import, but not nested plugins
-
-### Fix
-
-- **log**: fix the subprocess.popen issue while redirecting sys.stdout
-- **ci**: fix pip install issue
-- **ci**: run publish pypi on main branch
-- small fixes about examples and type hint
-- app_path would use test script path if not set
-- **ci**: publish packages
-- typing "list" not available before python 3.9
-- **docs**: missing requirements
-- **ci**: use pip install . to include less files
-- **base**: remove redundant double quote in sdkconfig
diff --git a/pytest-embedded-qemu/README.md b/pytest-embedded-qemu/README.md
index 7d56c461..65a0c254 100644
--- a/pytest-embedded-qemu/README.md
+++ b/pytest-embedded-qemu/README.md
@@ -4,21 +4,17 @@ pytest embedded service for running tests on QEMU instead of the real target.
 
 Extra Functionalities:
 
-=== "`pytest-embedded-idf` activated"
+```{eval-rst}
+.. tabs::
 
-    - `app`: create the qemu bootable image automatically by the built binaries.
-    - `qemu`: enable the fixture
-    - `dut`: duplicate the `qemu` output to `pexpect_proc`.
+   .. group-tab:: `pytest-embedded-idf` activated
 
-=== "`pytest-embedded-idf` NOT activated"
+        - `app`: create the qemu bootable image automatically by the built binaries.
+        - `qemu`: enable the fixture
+        - `dut`: duplicate the `qemu` output to `pexpect_proc`.
 
-    - `qemu`: enable the fixture
-    - `dut`: duplicate the `qemu` output to `pexpect_proc`.
+   .. group-tab:: `pytest-embedded-idf` NOT activated
 
-Used CLI Options:
-
-- `qemu-image-path`
-- `qemu-prog-path`
-- `qemu-cli-args`
-- `qemu-cli-extra-args`
-- `qemu-log-path`
+        - `qemu`: enable the fixture
+        - `dut`: duplicate the `qemu` output to `pexpect_proc`.
+```
diff --git a/pytest-embedded-serial-esp/README.md b/pytest-embedded-serial-esp/README.md
index 4741e6b2..9e612b78 100644
--- a/pytest-embedded-serial-esp/README.md
+++ b/pytest-embedded-serial-esp/README.md
@@ -5,7 +5,3 @@ pytest embedded service for testing espressif boards via serial ports
 Extra Functionalities:
 
 - `serial`: detect and confirm target and port by `esptool` automatically.
-
-Used CLI Options:
-
-- `target`
diff --git a/pytest-embedded-serial-esp/pytest_embedded_serial_esp/serial.py b/pytest-embedded-serial-esp/pytest_embedded_serial_esp/serial.py
index 63549382..e2be2d1f 100644
--- a/pytest-embedded-serial-esp/pytest_embedded_serial_esp/serial.py
+++ b/pytest-embedded-serial-esp/pytest_embedded_serial_esp/serial.py
@@ -162,20 +162,15 @@ def _post_init(self):
 
     def use_esptool(hard_reset_after: bool = True, no_stub: bool = False):
         """
-        1. close the redirect serial process if exists
-        2. close the serial connection
-        3. use esptool to connect to the serial port and call `run_stub()`
-        4. call to the decorated function, could use `self.stub` inside the function as the stubbed loader
-        5. call `hard_reset()`
-        5. create the redirect serial process again
+        1. tell the redirect serial thread to stop reading from the `pyserial` instance
+        2. esptool reuse the `pyserial` instance and call `run_stub()`
+        3. call to the decorated function, could use `self.stub` inside the function as the stubbed loader
+        4. call `hard_reset()`, if `hard_reset_after` is True
+        5. tell the redirect serial thread to continue reading from serial
 
         Args:
             hard_reset_after: run hard reset after
             no_stub: disable launching the flasher stub
-
-        Warning:
-            the real `pyserial` object must be created inside `self._forward_io`,
-                The `pyserial` object can't be pickled when using multiprocessing.Process
         """
 
         def decorator(func):
diff --git a/pytest-embedded-serial/README.md b/pytest-embedded-serial/README.md
index ee0dff1a..28ece97d 100644
--- a/pytest-embedded-serial/README.md
+++ b/pytest-embedded-serial/README.md
@@ -6,7 +6,3 @@ Extra Functionalities:
 
 - `serial`: enable the fixture
 - `dut`: duplicate the `serial` output to `pexpect_proc`.
-
-Used CLI Options:
-
-- `port`
diff --git a/pytest-embedded-serial/pytest_embedded_serial/serial.py b/pytest-embedded-serial/pytest_embedded_serial/serial.py
index 7b559726..2b06fa8c 100644
--- a/pytest-embedded-serial/pytest_embedded_serial/serial.py
+++ b/pytest-embedded-serial/pytest_embedded_serial/serial.py
@@ -23,9 +23,7 @@ class Serial:
         proc (pyserial.Serial): process created by `serial.serial_for_url()`
 
     Warning:
-        - the real `pyserial` object must be created inside `self._forward_io`,
-          The `pyserial` object can't be pickled when using multiprocessing.Process
-        - make sure this `Serial` __init__ run the last in MRO, it would create and start the redirect serial process
+        - make sure this `Serial.__init__()` run the last in MRO, it would create and start the redirect serial process
     """
 
     DEFAULT_BAUDRATE = 115200
@@ -98,6 +96,8 @@ def start_redirect_thread(self) -> None:
         if self._redirect_thread and self._redirect_thread.is_alive():
             return
 
+        # Here the reason why we're still using thread is,
+        # the `pyserial` object can't be pickled when using multiprocessing.Process
         self._redirect_thread = _SerialRedirectThread(self._q, self.proc)
         self._redirect_thread.start()
 
diff --git a/pytest-embedded/README.md b/pytest-embedded/README.md
index a9529ea9..f49c7e7a 100644
--- a/pytest-embedded/README.md
+++ b/pytest-embedded/README.md
@@ -1,7 +1,3 @@
 ### pytest-embedded
 
 A pytest plugin for embedded systems. Could activate different services for extra functionalities.
-
-Used CLI Options:
-
-- `app_path`
diff --git a/pytest-embedded/pytest_embedded/dut.py b/pytest-embedded/pytest_embedded/dut.py
index 3b65e457..5e618e2d 100644
--- a/pytest-embedded/pytest_embedded/dut.py
+++ b/pytest-embedded/pytest_embedded/dut.py
@@ -101,7 +101,7 @@ def wrapper(
     @_pexpect_func  # noqa
     def expect(self, pattern, **kwargs) -> Match:  # noqa
         """
-        Expect the `pattern` from the internal buffer. All the arguments would pass to `pexpect.expect()`.
+        Expect the `pattern` from the internal buffer. All the arguments will be passed to `pexpect.expect()`.
 
         Args:
             pattern: string, or compiled regex, or a list of string and compiled regex.
@@ -112,16 +112,17 @@ def expect(self, pattern, **kwargs) -> Match:  # noqa
                 Otherwise match any of them could pass
 
         Returns:
-            (AnyStr): if you're matching pexpect.EOF or pexpect.TIMEOUT to get all the current buffers.
+            `AnyStr` or `re.Match`
 
-            (re.Match): if matched given string.
+            - `AnyStr`: if you're matching `pexpect.EOF` or `pexpect.TIMEOUT` to get all the current buffers.
+            - `re.Match`: if matched given string.
         """
         return self.pexpect_proc.expect(pattern, **kwargs)
 
     @_pexpect_func  # noqa
     def expect_exact(self, pattern, **kwargs) -> Match:  # noqa
         """
-        Expect the `pattern` from the internal buffer. All the arguments would pass to `pexpect.expect_exact()`.
+        Expect the `pattern` from the internal buffer. All the arguments will be passed to `pexpect.expect_exact()`.
 
         Args:
             pattern: string, or a list of string
@@ -132,9 +133,10 @@ def expect_exact(self, pattern, **kwargs) -> Match:  # noqa
                 Otherwise match any of them could pass
 
         Returns:
-            (AnyStr): if you're matching pexpect.EOF or pexpect.TIMEOUT to get all the current buffers.
+            `AnyStr` or `re.Match`
 
-            (re.Match): if matched given string.
+            - `AnyStr`: if you're matching `pexpect.EOF` or `pexpect.TIMEOUT` to get all the current buffers.
+            - `re.Match`: if matched given string.
         """
         return self.pexpect_proc.expect_exact(pattern, **kwargs)
 
@@ -147,7 +149,7 @@ def expect_unity_test_output(
         """
         Expect a unity test summary block and parse the output into junit report.
 
-        Would combine the junit report into the main one if you use `pytest --junitxml` feature.
+        Would combine the junit report into the main one if you use ``pytest --junitxml`` feature.
 
         Args:
             remove_asci_escape_code: remove asci escape code in the message field. (default: True)
@@ -155,10 +157,10 @@ def expect_unity_test_output(
             extra_before: would append before the expected bytes.
                 Use this argument when need to run `expect` functions between one unity test call.
 
-        Notes:
+        Note:
             - Would raise AssertionError at the end of the test if any unity test case result is "FAIL"
-            - Would raise TIMEOUT exception at the end of the test if any unity test case execution took longer
-                than timeout value
+            - Would raise TIMEOUT exception at the end of the test \
+                if any unity test case execution took longer than timeout value
 
         Warning:
             - All unity test cases record would be missed if the final report block is uncaught.
@@ -182,7 +184,7 @@ def run_all_single_board_cases(
         reset: bool = False,
         timeout: float = 30,
         run_ignore_cases: bool = False,
-    ):
+    ) -> None:
         """
         Run all multi_stage cases
 
@@ -193,6 +195,6 @@ def run_all_single_board_cases(
             run_ignore_cases: run ignored test cases or not
 
         Warning:
-            requires enable service `idf`
+            requires enable service ``idf``
         """
         pass
diff --git a/pytest-embedded/pytest_embedded/log.py b/pytest-embedded/pytest_embedded/log.py
index 1c1284c3..6ed1958d 100644
--- a/pytest-embedded/pytest_embedded/log.py
+++ b/pytest-embedded/pytest_embedded/log.py
@@ -55,17 +55,15 @@ def buffer_debug_str(self):
             placeholder=f'... (total {len(self.buffer)} bytes)',
         )
 
-    def read_nonblocking(self, size=1, timeout=-1) -> bytes:
+    def read_nonblocking(self, size: int = 1, timeout: int = -1) -> bytes:
         """
         Since we're using real file stream, here we only raise an EOF error when the file stream has been closed.
         This could solve the `os.read()` blocked issue.
 
-        :return: String containing the bytes read
-
         Args:
-            size (int): Read at most *size* bytes.
-            timeout (float): Wait timeout seconds for file descriptor to be
-                ready to read. When -1 (default), use self.timeout. When 0, poll.
+            size: Read at most *size* bytes.
+            timeout: Wait timeout seconds for file descriptor to be
+                ready to read. When -1 (default), use `self.timeout`. When 0, poll.
 
         Returns:
             String containing the bytes read
@@ -115,7 +113,7 @@ def live_print_call(*args, msg_queue: Optional[MessageQueue] = None, expect_retu
         msg_queue: `MessageQueue` instance, would redirect to message queue instead of sys.stdout if specified
         expect_returncode: expect return code. (Default 0). Would raise exception when return code is different
 
-    Notes:
+    Note:
         This function behaves the same as `subprocess.call()`, it would block your current process.
     """
     default_kwargs = {
@@ -210,12 +208,11 @@ def terminate(self):
         super().terminate()
 
     def write(self, s: AnyStr) -> None:
-        """
+        r"""
         Write to `stdin` via `stdin.write`.
 
-        If the input is `str`, will encode to `bytes` and add a b'\\n' automatically in the end.
-
-        if the input is `bytes`, will pass this directly.
+        - If the input is `str`, will encode to `bytes` and add a b'\\n' automatically in the end.
+        - If the input is `bytes`, will pass this directly.
 
         Args:
             s: bytes or str
diff --git a/pytest-embedded/pytest_embedded/plugin.py b/pytest-embedded/pytest_embedded/plugin.py
index 6c2db4ac..b245331b 100644
--- a/pytest-embedded/pytest_embedded/plugin.py
+++ b/pytest-embedded/pytest_embedded/plugin.py
@@ -341,7 +341,7 @@ def multi_dut_fixture(func) -> t.Callable[..., t.Union[t.Any, t.Tuple[t.Any]]]:
     """
     Apply the multi-dut arguments to each fixture.
 
-    Notes:
+    Note:
         Run the `func(*args, **kwargs)` for multiple times by iterating all `kwargs` via `itemgetter`
 
         For example:
@@ -387,7 +387,7 @@ def multi_dut_generator_fixture(
     """
     Apply the multi-dut arguments to each fixture.
 
-    Notes:
+    Note:
         Run the `func()` for multiple times by iterating all `kwargs` via `itemgetter`. Auto call `close()` or
         `terminate()` method of the object after it yield back.
 
diff --git a/pytest-embedded/pytest_embedded/utils.py b/pytest-embedded/pytest_embedded/utils.py
index e34bd56c..60c4f747 100644
--- a/pytest-embedded/pytest_embedded/utils.py
+++ b/pytest-embedded/pytest_embedded/utils.py
@@ -1,4 +1,5 @@
 import dataclasses
+import functools
 import importlib
 import logging
 import os
@@ -117,10 +118,11 @@ def to_list(s: _T) -> t.List[_T]:
         s: Anything
 
     Returns:
-        List (list[_T]):
-            - `list(s)` (List. If `s` is a tuple or a set.
-            - itself. If `s` is a list.
-            - `[s]`. If `s` is other types.
+        List (list[_T])
+
+        - `list(s)` (List. If `s` is a tuple or a set.
+        - itself. If `s` is a list.
+        - `[s]`. If `s` is other types.
     """
     if not s:
         return s
@@ -231,22 +233,23 @@ def lazy_load(
     Returns:
         __getattr__ function
 
-    Examples:
-        ```python
-        __getattr__ = lazy_load(
-            importlib.import_module(__name__),
-            {
-                'IdfApp': IdfApp,
-                'LinuxDut': LinuxDut,
-                'LinuxSerial': LinuxSerial,
-                'CaseTester': CaseTester,
-            },
-            {
-                'IdfSerial': '.serial',
-                'IdfDut': '.dut',
-            },
-        )
-        ```
+    Example:
+
+        ::
+
+            __getattr__ = lazy_load(
+                importlib.import_module(__name__),
+                {
+                    'IdfApp': IdfApp,
+                    'LinuxDut': LinuxDut,
+                    'LinuxSerial': LinuxSerial,
+                    'CaseTester': CaseTester,
+                },
+                {
+                    'IdfSerial': '.serial',
+                    'IdfDut': '.dut',
+                },
+            )
     """
 
     def __getattr__(object_name):
@@ -294,33 +297,36 @@ class _InjectMixinCls(metaclass=_InjectMixinMeta):
     - provide the autocompletion for the functions provided by the mixins
     - check the requirement at runtime
 
-    Examples:
+    Example:
+
+        ::
 
-        class IdfUnityMixin:
-            def foo(self):
-                print('foo from MixinOne')
+            class IdfUnityMixin:
+                def foo(self):
+                    print('foo from MixinOne')
 
 
-        class Test(_InjectMixinCls):
-            def __init__(self, **kwargs):
-                for k, v in kwargs.items():
-                    setattr(self, k, v)
+            class Test(_InjectMixinCls):
+                def __init__(self, **kwargs):
+                    for k, v in kwargs.items():
+                        setattr(self, k, v)
 
-            @_InjectMixinCls.require_services('idf')
-            def foo(self):
-                pass
+                @_InjectMixinCls.require_services('idf')
+                def foo(self):
+                    pass
 
 
-        # mro(): TestWithIdfUnityMixin, IdfUnityMixin, Test, object
-        s1 = Test(mixins=IdfUnityMixin)
-        # mro(): Test, object
-        s2 = Test()
-        s1.foo()  # foo from IdfUnityMixin
-        s2.foo()  # function foo requires enabling one of the service(s) idf.
+            # mro(): TestWithIdfUnityMixin, IdfUnityMixin, Test, object
+            s1 = Test(mixins=IdfUnityMixin)
+            # mro(): Test, object
+            s2 = Test()
+            s1.foo()  # foo from IdfUnityMixin
+            s2.foo()  # function foo requires enabling one of the service(s) idf.
     """
 
     def require_services(*services):
         def decorator(func):
+            @functools.wraps(func)
             def wrapped(self, *args, **kwargs):
                 based = False
                 for service in services: